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

keradus / PHP-CS-Fixer / 17279562118

27 Aug 2025 09:47PM UTC coverage: 94.693%. Remained the same
17279562118

push

github

keradus
CS

28316 of 29903 relevant lines covered (94.69%)

45.61 hits per line

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

98.61
/src/Fixer/ControlStructure/EmptyLoopConditionFixer.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\ControlStructure;
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
 *  style?: 'for'|'while',
32
 * }
33
 * @phpstan-type _AutogeneratedComputedConfiguration array{
34
 *  style: 'for'|'while',
35
 * }
36
 *
37
 * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
38
 *
39
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
40
 */
41
final class EmptyLoopConditionFixer extends AbstractFixer implements ConfigurableFixerInterface
42
{
43
    /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
44
    use ConfigurableFixerTrait;
45

46
    private const STYLE_FOR = 'for';
47

48
    private const STYLE_WHILE = 'while';
49

50
    private const TOKEN_LOOP_KINDS = [\T_FOR, \T_WHILE];
51

52
    public function getDefinition(): FixerDefinitionInterface
53
    {
54
        return new FixerDefinition(
3✔
55
            'Empty loop-condition must be in configured style.',
3✔
56
            [
3✔
57
                new CodeSample("<?php\nfor(;;) {\n    foo();\n}\n\ndo {\n    foo();\n} while(true); // do while\n"),
3✔
58
                new CodeSample("<?php\nwhile(true) {\n    foo();\n}\n", ['style' => self::STYLE_FOR]),
3✔
59
            ]
3✔
60
        );
3✔
61
    }
62

63
    /**
64
     * {@inheritdoc}
65
     *
66
     * Must run before NoExtraBlankLinesFixer, NoTrailingWhitespaceFixer.
67
     */
68
    public function getPriority(): int
69
    {
70
        return 1;
1✔
71
    }
72

73
    public function isCandidate(Tokens $tokens): bool
74
    {
75
        return $tokens->isAnyTokenKindsFound(self::TOKEN_LOOP_KINDS);
15✔
76
    }
77

78
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
79
    {
80
        if (self::STYLE_WHILE === $this->configuration['style']) {
15✔
81
            $candidateLoopKinds = [\T_FOR, \T_WHILE];
10✔
82
            $replacement = [new Token([\T_WHILE, 'while']), new Token([\T_WHITESPACE, ' ']), new Token('('), new Token([\T_STRING, 'true']), new Token(')')];
10✔
83

84
            $fixLoop = static function (int $index, int $openIndex, int $endIndex) use ($tokens, $replacement): void {
10✔
85
                if (self::isForLoopWithEmptyCondition($tokens, $index, $openIndex, $endIndex)) {
10✔
86
                    self::clearNotCommentsInRange($tokens, $index, $endIndex);
3✔
87
                    self::cloneAndInsert($tokens, $index, $replacement);
3✔
88
                } elseif (self::isWhileLoopWithEmptyCondition($tokens, $index, $openIndex, $endIndex)) {
10✔
89
                    $doIndex = self::getDoIndex($tokens, $index);
8✔
90

91
                    if (null !== $doIndex) {
8✔
92
                        self::clearNotCommentsInRange($tokens, $index, $tokens->getNextMeaningfulToken($endIndex)); // clear including `;`
6✔
93
                        $tokens->clearAt($doIndex);
6✔
94
                        self::cloneAndInsert($tokens, $doIndex, $replacement);
6✔
95
                    }
96
                }
97
            };
10✔
98
        } else { // self::STYLE_FOR
99
            $candidateLoopKinds = [\T_WHILE];
6✔
100
            $replacement = [new Token([\T_FOR, 'for']), new Token('('), new Token(';'), new Token(';'), new Token(')')];
6✔
101

102
            $fixLoop = static function (int $index, int $openIndex, int $endIndex) use ($tokens, $replacement): void {
6✔
103
                if (!self::isWhileLoopWithEmptyCondition($tokens, $index, $openIndex, $endIndex)) {
6✔
104
                    return;
3✔
105
                }
106

107
                $doIndex = self::getDoIndex($tokens, $index);
4✔
108

109
                if (null === $doIndex) {
4✔
110
                    self::clearNotCommentsInRange($tokens, $index, $endIndex);
3✔
111
                    self::cloneAndInsert($tokens, $index, $replacement);
3✔
112
                } else {
113
                    self::clearNotCommentsInRange($tokens, $index, $tokens->getNextMeaningfulToken($endIndex)); // clear including `;`
1✔
114
                    $tokens->clearAt($doIndex);
1✔
115
                    self::cloneAndInsert($tokens, $doIndex, $replacement);
1✔
116
                }
117
            };
6✔
118
        }
119

120
        for ($index = $tokens->count() - 1; $index > 0; --$index) {
15✔
121
            if ($tokens[$index]->isGivenKind($candidateLoopKinds)) {
15✔
122
                $openIndex = $tokens->getNextTokenOfKind($index, ['(']); // proceed to open '('
15✔
123
                $endIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex); // proceed to close ')'
15✔
124
                $fixLoop($index, $openIndex, $endIndex); // fix loop if needed
15✔
125
            }
126
        }
127
    }
128

129
    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
130
    {
131
        return new FixerConfigurationResolver([
24✔
132
            (new FixerOptionBuilder('style', 'Style of empty loop-condition.'))
24✔
133
                ->setAllowedTypes(['string'])
24✔
134
                ->setAllowedValues([self::STYLE_WHILE, self::STYLE_FOR])
24✔
135
                ->setDefault(self::STYLE_WHILE)
24✔
136
                ->getOption(),
24✔
137
        ]);
24✔
138
    }
139

140
    private static function clearNotCommentsInRange(Tokens $tokens, int $indexStart, int $indexEnd): void
141
    {
142
        for ($i = $indexStart; $i <= $indexEnd; ++$i) {
11✔
143
            if (!$tokens[$i]->isComment()) {
11✔
144
                $tokens->clearTokenAndMergeSurroundingWhitespace($i);
11✔
145
            }
146
        }
147
    }
148

149
    /**
150
     * @param list<Token> $replacement
151
     */
152
    private static function cloneAndInsert(Tokens $tokens, int $index, array $replacement): void
153
    {
154
        $replacementClones = [];
11✔
155

156
        foreach ($replacement as $token) {
11✔
157
            $replacementClones[] = clone $token;
11✔
158
        }
159

160
        $tokens->insertAt($index, $replacementClones);
11✔
161
    }
162

163
    private static function getDoIndex(Tokens $tokens, int $index): ?int
164
    {
165
        $endIndex = $tokens->getPrevMeaningfulToken($index);
11✔
166

167
        if (!$tokens[$endIndex]->equals('}')) {
11✔
168
            return null;
10✔
169
        }
170

171
        $startIndex = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $endIndex);
7✔
172
        $index = $tokens->getPrevMeaningfulToken($startIndex);
7✔
173

174
        return null === $index || !$tokens[$index]->isGivenKind(\T_DO) ? null : $index;
7✔
175
    }
176

177
    private static function isForLoopWithEmptyCondition(Tokens $tokens, int $index, int $openIndex, int $endIndex): bool
178
    {
179
        if (!$tokens[$index]->isGivenKind(\T_FOR)) {
10✔
180
            return false;
9✔
181
        }
182

183
        $index = $tokens->getNextMeaningfulToken($openIndex);
4✔
184

185
        if (null === $index || !$tokens[$index]->equals(';')) {
4✔
186
            return false;
×
187
        }
188

189
        $index = $tokens->getNextMeaningfulToken($index);
4✔
190

191
        return null !== $index && $tokens[$index]->equals(';') && $endIndex === $tokens->getNextMeaningfulToken($index);
4✔
192
    }
193

194
    private static function isWhileLoopWithEmptyCondition(Tokens $tokens, int $index, int $openIndex, int $endIndex): bool
195
    {
196
        if (!$tokens[$index]->isGivenKind(\T_WHILE)) {
15✔
197
            return false;
2✔
198
        }
199

200
        $index = $tokens->getNextMeaningfulToken($openIndex);
14✔
201

202
        return null !== $index && $tokens[$index]->equals([\T_STRING, 'true']) && $endIndex === $tokens->getNextMeaningfulToken($index);
14✔
203
    }
204
}
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