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

keradus / PHP-CS-Fixer / 19958239208

05 Dec 2025 09:13AM UTC coverage: 93.181% (-1.0%) from 94.158%
19958239208

push

github

keradus
chore: .php-cs-fixer.dist.php - remove no longer needed rule, 'expectedDeprecation' annotation does not exist for long time

28928 of 31045 relevant lines covered (93.18%)

44.49 hits per line

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

92.54
/src/Tokenizer/Analyzer/ControlCaseStructuresAnalyzer.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\Tokenizer\Analyzer;
16

17
use PhpCsFixer\Tokenizer\Analyzer\Analysis\AbstractControlCaseStructuresAnalysis;
18
use PhpCsFixer\Tokenizer\Analyzer\Analysis\CaseAnalysis;
19
use PhpCsFixer\Tokenizer\Analyzer\Analysis\DefaultAnalysis;
20
use PhpCsFixer\Tokenizer\Analyzer\Analysis\EnumAnalysis;
21
use PhpCsFixer\Tokenizer\Analyzer\Analysis\MatchAnalysis;
22
use PhpCsFixer\Tokenizer\Analyzer\Analysis\SwitchAnalysis;
23
use PhpCsFixer\Tokenizer\FCT;
24
use PhpCsFixer\Tokenizer\Tokens;
25

26
/**
27
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
28
 *
29
 * @TODO 4.0: mark @internal
30
 */
31
final class ControlCaseStructuresAnalyzer
32
{
33
    private const SUPPORTED_TYPES_WITH_CASE_OR_DEFAULT = [
34
        \T_SWITCH,
35
        FCT::T_MATCH,
36
        FCT::T_ENUM,
37
    ];
38

39
    /**
40
     * @param list<int> $types Token types of interest of which analyses must be returned
41
     *
42
     * @return \Generator<int, AbstractControlCaseStructuresAnalysis>
43
     */
44
    public static function findControlStructures(Tokens $tokens, array $types): \Generator
45
    {
46
        if (\count($types) < 1) {
26✔
47
            return; // quick skip
×
48
        }
49

50
        foreach ($types as $type) {
26✔
51
            if (!\in_array($type, self::SUPPORTED_TYPES_WITH_CASE_OR_DEFAULT, true)) {
26✔
52
                throw new \InvalidArgumentException(\sprintf('Unexpected type "%d".', $type));
1✔
53
            }
54
        }
55

56
        if (!$tokens->isAnyTokenKindsFound($types)) {
25✔
57
            return; // quick skip
1✔
58
        }
59

60
        $depth = -1;
24✔
61

62
        /**
63
         * @var list<array{
64
         *     kind: null|int,
65
         *     index: int,
66
         *     brace_count: int,
67
         *     cases: list<array{index: int, open: int}>,
68
         *     default: null|array{index: int, open: int},
69
         *     alternative_syntax: bool,
70
         * }> $stack
71
         */
72
        $stack = [];
24✔
73
        $isTypeOfInterest = false;
24✔
74

75
        foreach ($tokens as $index => $token) {
24✔
76
            if ($token->isGivenKind(self::SUPPORTED_TYPES_WITH_CASE_OR_DEFAULT)) {
24✔
77
                ++$depth;
24✔
78

79
                $stack[$depth] = [
24✔
80
                    'kind' => $token->getId(),
24✔
81
                    'index' => $index,
24✔
82
                    'brace_count' => 0,
24✔
83
                    'cases' => [],
24✔
84
                    'default' => null,
24✔
85
                    'alternative_syntax' => false,
24✔
86
                ];
24✔
87

88
                $isTypeOfInterest = \in_array($stack[$depth]['kind'], $types, true);
24✔
89

90
                if ($token->isGivenKind(\T_SWITCH)) {
24✔
91
                    $index = $tokens->getNextMeaningfulToken($index);
24✔
92
                    $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
24✔
93

94
                    $stack[$depth]['open'] = $tokens->getNextMeaningfulToken($index);
24✔
95
                    $stack[$depth]['alternative_syntax'] = $tokens[$stack[$depth]['open']]->equals(':');
24✔
96
                } elseif ($token->isGivenKind(FCT::T_MATCH)) {
3✔
97
                    $index = $tokens->getNextMeaningfulToken($index);
3✔
98
                    $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
3✔
99

100
                    $stack[$depth]['open'] = $tokens->getNextMeaningfulToken($index);
3✔
101
                } elseif ($token->isGivenKind(FCT::T_ENUM)) {
3✔
102
                    $stack[$depth]['open'] = $tokens->getNextTokenOfKind($index, ['{']);
3✔
103
                }
104

105
                continue;
24✔
106
            }
107

108
            if ($depth < 0) {
24✔
109
                continue;
24✔
110
            }
111

112
            if ($token->equals('{')) {
24✔
113
                ++$stack[$depth]['brace_count'];
19✔
114

115
                continue;
19✔
116
            }
117

118
            if ($token->equals('}')) {
24✔
119
                --$stack[$depth]['brace_count'];
19✔
120

121
                if (0 === $stack[$depth]['brace_count']) {
19✔
122
                    if ($stack[$depth]['alternative_syntax']) {
19✔
123
                        continue;
1✔
124
                    }
125

126
                    if ($isTypeOfInterest) {
18✔
127
                        $stack[$depth]['end'] = $index;
18✔
128

129
                        yield $stack[$depth]['index'] => self::buildControlCaseStructureAnalysis($stack[$depth]);
18✔
130
                    }
131

132
                    array_pop($stack);
18✔
133
                    --$depth;
18✔
134

135
                    if ($depth < -1) { // @phpstan-ignore-line
18✔
136
                        throw new \RuntimeException('Analysis depth count failure.');
×
137
                    }
138

139
                    if (isset($stack[$depth]['kind'])) {
18✔
140
                        $isTypeOfInterest = \in_array($stack[$depth]['kind'], $types, true);
3✔
141
                    }
142
                }
143

144
                continue;
18✔
145
            }
146

147
            if ($tokens[$index]->isGivenKind(\T_ENDSWITCH)) {
24✔
148
                if (!$stack[$depth]['alternative_syntax']) {
6✔
149
                    throw new \RuntimeException('Analysis syntax failure, unexpected "T_ENDSWITCH".');
×
150
                }
151

152
                if (\T_SWITCH !== $stack[$depth]['kind']) {
6✔
153
                    throw new \RuntimeException('Analysis type failure, unexpected "T_ENDSWITCH".');
×
154
                }
155

156
                if (0 !== $stack[$depth]['brace_count']) {
6✔
157
                    throw new \RuntimeException('Analysis count failure, unexpected "T_ENDSWITCH".');
×
158
                }
159

160
                $index = $tokens->getNextTokenOfKind($index, [';', [\T_CLOSE_TAG]]);
6✔
161

162
                if ($isTypeOfInterest) {
6✔
163
                    $stack[$depth]['end'] = $index;
6✔
164

165
                    yield $stack[$depth]['index'] => self::buildControlCaseStructureAnalysis($stack[$depth]);
6✔
166
                }
167

168
                array_pop($stack);
6✔
169
                --$depth;
6✔
170

171
                if ($depth < -1) { // @phpstan-ignore-line
6✔
172
                    throw new \RuntimeException('Analysis depth count failure ("T_ENDSWITCH").');
×
173
                }
174

175
                if (isset($stack[$depth]['kind'])) {
6✔
176
                    $isTypeOfInterest = \in_array($stack[$depth]['kind'], $types, true);
3✔
177
                }
178
            }
179

180
            if (!$isTypeOfInterest) {
24✔
181
                continue; // don't bother to analyse stuff that caller is not interested in
3✔
182
            }
183

184
            if ($token->isGivenKind(\T_CASE)) {
24✔
185
                $stack[$depth]['cases'][] = ['index' => $index, 'open' => self::findCaseOpen($tokens, $stack[$depth]['kind'], $index)];
23✔
186
            } elseif ($token->isGivenKind(\T_DEFAULT)) {
24✔
187
                if (null !== $stack[$depth]['default']) {
8✔
188
                    throw new \RuntimeException('Analysis multiple "default" found.');
×
189
                }
190

191
                $stack[$depth]['default'] = ['index' => $index, 'open' => self::findDefaultOpen($tokens, $stack[$depth]['kind'], $index)];
8✔
192
            }
193
        }
194
    }
195

196
    /**
197
     * @param array{
198
     *     kind: int,
199
     *     index: int,
200
     *     open: int,
201
     *     end: int,
202
     *     cases: list<array{index: int, open: int}>,
203
     *     default: null|array{index: int, open: int},
204
     * } $analysis
205
     */
206
    private static function buildControlCaseStructureAnalysis(array $analysis): AbstractControlCaseStructuresAnalysis
207
    {
208
        $default = null === $analysis['default']
24✔
209
            ? null
17✔
210
            : new DefaultAnalysis($analysis['default']['index'], $analysis['default']['open']);
8✔
211

212
        $cases = [];
24✔
213

214
        foreach ($analysis['cases'] as $case) {
24✔
215
            $cases[$case['index']] = new CaseAnalysis($case['index'], $case['open']);
23✔
216
        }
217

218
        sort($cases);
24✔
219

220
        if (\T_SWITCH === $analysis['kind']) {
24✔
221
            return new SwitchAnalysis(
23✔
222
                $analysis['index'],
23✔
223
                $analysis['open'],
23✔
224
                $analysis['end'],
23✔
225
                $cases,
23✔
226
                $default
23✔
227
            );
23✔
228
        }
229

230
        if (FCT::T_ENUM === $analysis['kind']) {
2✔
231
            return new EnumAnalysis(
1✔
232
                $analysis['index'],
1✔
233
                $analysis['open'],
1✔
234
                $analysis['end'],
1✔
235
                $cases
1✔
236
            );
1✔
237
        }
238

239
        if (FCT::T_MATCH === $analysis['kind']) {
1✔
240
            return new MatchAnalysis(
1✔
241
                $analysis['index'],
1✔
242
                $analysis['open'],
1✔
243
                $analysis['end'],
1✔
244
                $default
1✔
245
            );
1✔
246
        }
247

248
        throw new \InvalidArgumentException(\sprintf('Unexpected type "%d".', $analysis['kind']));
×
249
    }
250

251
    private static function findCaseOpen(Tokens $tokens, int $kind, int $index): int
252
    {
253
        if (\T_SWITCH === $kind) {
23✔
254
            $ternariesCount = 0;
23✔
255

256
            --$index;
23✔
257
            while (true) {
23✔
258
                ++$index;
23✔
259

260
                if ($tokens[$index]->equalsAny(['(', '{'])) { // skip constructs
23✔
261
                    $type = Tokens::detectBlockType($tokens[$index]);
3✔
262
                    $index = $tokens->findBlockEnd($type['type'], $index);
3✔
263

264
                    continue;
3✔
265
                }
266

267
                if ($tokens[$index]->equals('?')) {
23✔
268
                    ++$ternariesCount;
1✔
269

270
                    continue;
1✔
271
                }
272

273
                if ($tokens[$index]->equalsAny([':', ';'])) {
23✔
274
                    if (0 === $ternariesCount) {
23✔
275
                        break;
23✔
276
                    }
277

278
                    --$ternariesCount;
1✔
279
                }
280
            }
281

282
            return $index;
23✔
283
        }
284

285
        if (FCT::T_ENUM === $kind) {
1✔
286
            return $tokens->getNextTokenOfKind($index, ['=', ';']);
1✔
287
        }
288

289
        throw new \InvalidArgumentException(\sprintf('Unexpected case for type "%d".', $kind));
×
290
    }
291

292
    private static function findDefaultOpen(Tokens $tokens, int $kind, int $index): int
293
    {
294
        if (\T_SWITCH === $kind) {
8✔
295
            return $tokens->getNextTokenOfKind($index, [':', ';']);
7✔
296
        }
297

298
        if (FCT::T_MATCH === $kind) {
1✔
299
            return $tokens->getNextTokenOfKind($index, [[\T_DOUBLE_ARROW]]);
1✔
300
        }
301

302
        throw new \InvalidArgumentException(\sprintf('Unexpected default for type "%d".', $kind));
×
303
    }
304
}
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