OrderedInterfacesFixer::applyFix()   F
last analyzed

Complexity

Conditions 19
Paths 225

Size

Total Lines 93
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 52
c 1
b 0
f 0
dl 0
loc 93
rs 3.3708
cc 19
nc 225
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of PHP CS Fixer.
7
 *
8
 * (c) Fabien Potencier <[email protected]>
9
 *     Dariusz Rumiński <[email protected]>
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 <[email protected]>
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(
77
            'Orders the interfaces in an `implements` or `interface extends` clause.',
78
            [
79
                new CodeSample(
80
                    "<?php\n\nfinal class ExampleA implements Gamma, Alpha, Beta {}\n\ninterface ExampleB extends Gamma, Alpha, Beta {}\n"
81
                ),
82
                new CodeSample(
83
                    "<?php\n\nfinal class ExampleA implements Gamma, Alpha, Beta {}\n\ninterface ExampleB extends Gamma, Alpha, Beta {}\n",
84
                    [self::OPTION_DIRECTION => self::DIRECTION_DESCEND]
85
                ),
86
                new CodeSample(
87
                    "<?php\n\nfinal class ExampleA implements MuchLonger, Short, Longer {}\n\ninterface ExampleB extends MuchLonger, Short, Longer {}\n",
88
                    [self::OPTION_ORDER => self::ORDER_LENGTH]
89
                ),
90
                new CodeSample(
91
                    "<?php\n\nfinal class ExampleA implements MuchLonger, Short, Longer {}\n\ninterface ExampleB extends MuchLonger, Short, Longer {}\n",
92
                    [
93
                        self::OPTION_ORDER => self::ORDER_LENGTH,
94
                        self::OPTION_DIRECTION => self::DIRECTION_DESCEND,
95
                    ]
96
                ),
97
            ],
98
            null,
99
            "Risky for `implements` when specifying both an interface and its parent interface, because PHP doesn't break on `parent, child` but does on `child, parent`."
100
        );
101
    }
102
103
    /**
104
     * {@inheritdoc}
105
     */
106
    public function isCandidate(Tokens $tokens): bool
107
    {
108
        return $tokens->isTokenKindFound(T_IMPLEMENTS)
109
            || $tokens->isAllTokenKindsFound([T_INTERFACE, T_EXTENDS]);
110
    }
111
112
    /**
113
     * {@inheritdoc}
114
     */
115
    public function isRisky(): bool
116
    {
117
        return true;
118
    }
119
120
    /**
121
     * {@inheritdoc}
122
     */
123
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
124
    {
125
        foreach ($tokens as $index => $token) {
126
            if (!$token->isGivenKind(T_IMPLEMENTS)) {
127
                if (!$token->isGivenKind(T_EXTENDS)) {
128
                    continue;
129
                }
130
131
                $nameTokenIndex = $tokens->getPrevMeaningfulToken($index);
132
                $interfaceTokenIndex = $tokens->getPrevMeaningfulToken($nameTokenIndex);
0 ignored issues
show
Bug introduced by
It seems like $nameTokenIndex can also be of type null; however, parameter $index of PhpCsFixer\Tokenizer\Tok...etPrevMeaningfulToken() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

132
                $interfaceTokenIndex = $tokens->getPrevMeaningfulToken(/** @scrutinizer ignore-type */ $nameTokenIndex);
Loading history...
133
                $interfaceToken = $tokens[$interfaceTokenIndex];
134
135
                if (!$interfaceToken->isGivenKind(T_INTERFACE)) {
136
                    continue;
137
                }
138
            }
139
140
            $interfaceIndex = 0;
141
            $interfaces = [['tokens' => []]];
142
143
            $implementsStart = $index + 1;
144
            $classStart = $tokens->getNextTokenOfKind($implementsStart, ['{']);
145
            $implementsEnd = $tokens->getPrevNonWhitespace($classStart);
0 ignored issues
show
Bug introduced by
It seems like $classStart can also be of type null; however, parameter $index of PhpCsFixer\Tokenizer\Tok...:getPrevNonWhitespace() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

145
            $implementsEnd = $tokens->getPrevNonWhitespace(/** @scrutinizer ignore-type */ $classStart);
Loading history...
146
147
            for ($i = $implementsStart; $i <= $implementsEnd; ++$i) {
148
                if ($tokens[$i]->equals(',')) {
149
                    ++$interfaceIndex;
150
                    $interfaces[$interfaceIndex] = ['tokens' => []];
151
152
                    continue;
153
                }
154
155
                $interfaces[$interfaceIndex]['tokens'][] = $tokens[$i];
156
            }
157
158
            if (1 === \count($interfaces)) {
159
                continue;
160
            }
161
162
            foreach ($interfaces as $interfaceIndex => $interface) {
163
                $interfaceTokens = Tokens::fromArray($interface['tokens'], false);
164
165
                $normalized = '';
166
                $actualInterfaceIndex = $interfaceTokens->getNextMeaningfulToken(-1);
167
168
                while ($interfaceTokens->offsetExists($actualInterfaceIndex)) {
169
                    $token = $interfaceTokens[$actualInterfaceIndex];
170
171
                    if (null === $token || $token->isComment() || $token->isWhitespace()) {
172
                        break;
173
                    }
174
175
                    $normalized .= str_replace('\\', ' ', $token->getContent());
176
                    ++$actualInterfaceIndex;
177
                }
178
179
                $interfaces[$interfaceIndex]['normalized'] = $normalized;
180
                $interfaces[$interfaceIndex]['originalIndex'] = $interfaceIndex;
181
            }
182
183
            usort($interfaces, function (array $first, array $second) {
184
                $score = self::ORDER_LENGTH === $this->configuration[self::OPTION_ORDER]
185
                    ? \strlen($first['normalized']) - \strlen($second['normalized'])
186
                    : strcasecmp($first['normalized'], $second['normalized']);
187
188
                if (self::DIRECTION_DESCEND === $this->configuration[self::OPTION_DIRECTION]) {
189
                    $score *= -1;
190
                }
191
192
                return $score;
193
            });
194
195
            $changed = false;
196
197
            foreach ($interfaces as $interfaceIndex => $interface) {
198
                if ($interface['originalIndex'] !== $interfaceIndex) {
199
                    $changed = true;
200
201
                    break;
202
                }
203
            }
204
205
            if (!$changed) {
206
                continue;
207
            }
208
209
            $newTokens = array_shift($interfaces)['tokens'];
210
211
            foreach ($interfaces as $interface) {
212
                array_push($newTokens, new Token(','), ...$interface['tokens']);
213
            }
214
215
            $tokens->overrideRange($implementsStart, $implementsEnd, $newTokens);
0 ignored issues
show
Bug introduced by
It seems like $implementsEnd can also be of type null; however, parameter $indexEnd of PhpCsFixer\Tokenizer\Tokens::overrideRange() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

215
            $tokens->overrideRange($implementsStart, /** @scrutinizer ignore-type */ $implementsEnd, $newTokens);
Loading history...
216
        }
217
    }
218
219
    /**
220
     * {@inheritdoc}
221
     */
222
    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
223
    {
224
        return new FixerConfigurationResolver([
225
            (new FixerOptionBuilder(self::OPTION_ORDER, 'How the interfaces should be ordered'))
226
                ->setAllowedValues(self::SUPPORTED_ORDER_OPTIONS)
227
                ->setDefault(self::ORDER_ALPHA)
228
                ->getOption(),
229
            (new FixerOptionBuilder(self::OPTION_DIRECTION, 'Which direction the interfaces should be ordered'))
230
                ->setAllowedValues(self::SUPPORTED_DIRECTION_OPTIONS)
231
                ->setDefault(self::DIRECTION_ASCEND)
232
                ->getOption(),
233
        ]);
234
    }
235
}
236