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

keradus / PHP-CS-Fixer / 16018263876

02 Jul 2025 06:58AM UTC coverage: 94.846% (-0.002%) from 94.848%
16018263876

push

github

keradus
debug2

28193 of 29725 relevant lines covered (94.85%)

45.34 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
final class ControlCaseStructuresAnalyzer
27
{
28
    private const SUPPORTED_TYPES_WITH_CASE_OR_DEFAULT = [
29
        T_SWITCH,
30
        FCT::T_MATCH,
31
        FCT::T_ENUM,
32
    ];
33

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

45
        foreach ($types as $type) {
26✔
46
            if (!\in_array($type, self::SUPPORTED_TYPES_WITH_CASE_OR_DEFAULT, true)) {
26✔
47
                throw new \InvalidArgumentException(\sprintf('Unexpected type "%d".', $type));
1✔
48
            }
49
        }
50

51
        if (!$tokens->isAnyTokenKindsFound($types)) {
25✔
52
            return; // quick skip
1✔
53
        }
54

55
        $depth = -1;
24✔
56

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

70
        foreach ($tokens as $index => $token) {
24✔
71
            if ($token->isGivenKind(self::SUPPORTED_TYPES_WITH_CASE_OR_DEFAULT)) {
24✔
72
                ++$depth;
24✔
73

74
                $stack[$depth] = [
24✔
75
                    'kind' => $token->getId(),
24✔
76
                    'index' => $index,
24✔
77
                    'brace_count' => 0,
24✔
78
                    'cases' => [],
24✔
79
                    'default' => null,
24✔
80
                    'alternative_syntax' => false,
24✔
81
                ];
24✔
82

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

85
                if ($token->isGivenKind(T_SWITCH)) {
24✔
86
                    $index = $tokens->getNextMeaningfulToken($index);
24✔
87
                    $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
24✔
88

89
                    $stack[$depth]['open'] = $tokens->getNextMeaningfulToken($index);
24✔
90
                    $stack[$depth]['alternative_syntax'] = $tokens[$stack[$depth]['open']]->equals(':');
24✔
91
                } elseif ($token->isGivenKind(FCT::T_MATCH)) {
3✔
92
                    $index = $tokens->getNextMeaningfulToken($index);
3✔
93
                    $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $index);
3✔
94

95
                    $stack[$depth]['open'] = $tokens->getNextMeaningfulToken($index);
3✔
96
                } elseif ($token->isGivenKind(FCT::T_ENUM)) {
3✔
97
                    $stack[$depth]['open'] = $tokens->getNextTokenOfKind($index, ['{']);
3✔
98
                }
99

100
                continue;
24✔
101
            }
102

103
            if ($depth < 0) {
24✔
104
                continue;
24✔
105
            }
106

107
            if ($token->equals('{')) {
24✔
108
                ++$stack[$depth]['brace_count'];
19✔
109

110
                continue;
19✔
111
            }
112

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

116
                if (0 === $stack[$depth]['brace_count']) {
19✔
117
                    if ($stack[$depth]['alternative_syntax']) {
19✔
118
                        continue;
1✔
119
                    }
120

121
                    if ($isTypeOfInterest) {
18✔
122
                        $stack[$depth]['end'] = $index;
18✔
123

124
                        yield $stack[$depth]['index'] => self::buildControlCaseStructureAnalysis($stack[$depth]);
18✔
125
                    }
126

127
                    array_pop($stack);
18✔
128
                    --$depth;
18✔
129

130
                    if ($depth < -1) { // @phpstan-ignore-line
18✔
131
                        throw new \RuntimeException('Analysis depth count failure.');
×
132
                    }
133

134
                    if (isset($stack[$depth]['kind'])) {
18✔
135
                        $isTypeOfInterest = \in_array($stack[$depth]['kind'], $types, true);
3✔
136
                    }
137
                }
138

139
                continue;
18✔
140
            }
141

142
            if ($tokens[$index]->isGivenKind(T_ENDSWITCH)) {
24✔
143
                if (!$stack[$depth]['alternative_syntax']) {
6✔
144
                    throw new \RuntimeException('Analysis syntax failure, unexpected "T_ENDSWITCH".');
×
145
                }
146

147
                if (T_SWITCH !== $stack[$depth]['kind']) {
6✔
148
                    throw new \RuntimeException('Analysis type failure, unexpected "T_ENDSWITCH".');
×
149
                }
150

151
                if (0 !== $stack[$depth]['brace_count']) {
6✔
152
                    throw new \RuntimeException('Analysis count failure, unexpected "T_ENDSWITCH".');
×
153
                }
154

155
                $index = $tokens->getNextTokenOfKind($index, [';', [T_CLOSE_TAG]]);
6✔
156

157
                if ($isTypeOfInterest) {
6✔
158
                    $stack[$depth]['end'] = $index;
6✔
159

160
                    yield $stack[$depth]['index'] => self::buildControlCaseStructureAnalysis($stack[$depth]);
6✔
161
                }
162

163
                array_pop($stack);
6✔
164
                --$depth;
6✔
165

166
                if ($depth < -1) { // @phpstan-ignore-line
6✔
167
                    throw new \RuntimeException('Analysis depth count failure ("T_ENDSWITCH").');
×
168
                }
169

170
                if (isset($stack[$depth]['kind'])) {
6✔
171
                    $isTypeOfInterest = \in_array($stack[$depth]['kind'], $types, true);
3✔
172
                }
173
            }
174

175
            if (!$isTypeOfInterest) {
24✔
176
                continue; // don't bother to analyze stuff that caller is not interested in
3✔
177
            }
178

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

186
                $stack[$depth]['default'] = ['index' => $index, 'open' => self::findDefaultOpen($tokens, $stack[$depth]['kind'], $index)];
8✔
187
            }
188
        }
189
    }
190

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

207
        $cases = [];
24✔
208

209
        foreach ($analysis['cases'] as $case) {
24✔
210
            $cases[$case['index']] = new CaseAnalysis($case['index'], $case['open']);
23✔
211
        }
212

213
        sort($cases);
24✔
214

215
        if (T_SWITCH === $analysis['kind']) {
24✔
216
            return new SwitchAnalysis(
23✔
217
                $analysis['index'],
23✔
218
                $analysis['open'],
23✔
219
                $analysis['end'],
23✔
220
                $cases,
23✔
221
                $default
23✔
222
            );
23✔
223
        }
224

225
        if (FCT::T_ENUM === $analysis['kind']) {
2✔
226
            return new EnumAnalysis(
1✔
227
                $analysis['index'],
1✔
228
                $analysis['open'],
1✔
229
                $analysis['end'],
1✔
230
                $cases
1✔
231
            );
1✔
232
        }
233

234
        if (FCT::T_MATCH === $analysis['kind']) {
1✔
235
            return new MatchAnalysis(
1✔
236
                $analysis['index'],
1✔
237
                $analysis['open'],
1✔
238
                $analysis['end'],
1✔
239
                $default
1✔
240
            );
1✔
241
        }
242

243
        throw new \InvalidArgumentException(\sprintf('Unexpected type "%d".', $analysis['kind']));
×
244
    }
245

246
    private static function findCaseOpen(Tokens $tokens, int $kind, int $index): int
247
    {
248
        if (T_SWITCH === $kind) {
23✔
249
            $ternariesCount = 0;
23✔
250

251
            --$index;
23✔
252
            while (true) {
23✔
253
                ++$index;
23✔
254

255
                if ($tokens[$index]->equalsAny(['(', '{'])) { // skip constructs
23✔
256
                    $type = Tokens::detectBlockType($tokens[$index]);
3✔
257
                    $index = $tokens->findBlockEnd($type['type'], $index);
3✔
258

259
                    continue;
3✔
260
                }
261

262
                if ($tokens[$index]->equals('?')) {
23✔
263
                    ++$ternariesCount;
1✔
264

265
                    continue;
1✔
266
                }
267

268
                if ($tokens[$index]->equalsAny([':', ';'])) {
23✔
269
                    if (0 === $ternariesCount) {
23✔
270
                        break;
23✔
271
                    }
272

273
                    --$ternariesCount;
1✔
274
                }
275
            }
276

277
            return $index;
23✔
278
        }
279

280
        if (FCT::T_ENUM === $kind) {
1✔
281
            return $tokens->getNextTokenOfKind($index, ['=', ';']);
1✔
282
        }
283

284
        throw new \InvalidArgumentException(\sprintf('Unexpected case for type "%d".', $kind));
×
285
    }
286

287
    private static function findDefaultOpen(Tokens $tokens, int $kind, int $index): int
288
    {
289
        if (T_SWITCH === $kind) {
8✔
290
            return $tokens->getNextTokenOfKind($index, [':', ';']);
7✔
291
        }
292

293
        if (FCT::T_MATCH === $kind) {
1✔
294
            return $tokens->getNextTokenOfKind($index, [[T_DOUBLE_ARROW]]);
1✔
295
        }
296

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