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

keradus / PHP-CS-Fixer / 17678835382

12 Sep 2025 03:24PM UTC coverage: 94.69% (-0.06%) from 94.75%
17678835382

push

github

keradus
fix typo

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

1042 existing lines in 177 files now uncovered.

28424 of 30018 relevant lines covered (94.69%)

45.5 hits per line

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

98.32
/src/Fixer/StringNotation/StringLengthToEmptyFixer.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\StringNotation;
16

17
use PhpCsFixer\AbstractFunctionReferenceFixer;
18
use PhpCsFixer\FixerDefinition\CodeSample;
19
use PhpCsFixer\FixerDefinition\FixerDefinition;
20
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
21
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
22
use PhpCsFixer\Tokenizer\Token;
23
use PhpCsFixer\Tokenizer\Tokens;
24

25
/**
26
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
27
 */
28
final class StringLengthToEmptyFixer extends AbstractFunctionReferenceFixer
29
{
30
    public function getDefinition(): FixerDefinitionInterface
31
    {
32
        return new FixerDefinition(
3✔
33
            'String tests for empty must be done against `\'\'`, not with `strlen`.',
3✔
34
            [new CodeSample("<?php \$a = 0 === strlen(\$b) || \\STRLEN(\$c) < 1;\n")],
3✔
35
            null,
3✔
36
            'Risky when `strlen` is overridden, when called using a `stringable` object, also no longer triggers warning when called using non-string(able).'
3✔
37
        );
3✔
38
    }
39

40
    /**
41
     * {@inheritdoc}
42
     *
43
     * Must run before NoExtraBlankLinesFixer, NoTrailingWhitespaceFixer.
44
     * Must run after NoSpacesInsideParenthesisFixer, SpacesInsideParenthesesFixer.
45
     */
46
    public function getPriority(): int
47
    {
48
        return 1;
1✔
49
    }
50

51
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
52
    {
53
        $argumentsAnalyzer = new ArgumentsAnalyzer();
37✔
54

55
        foreach ($this->findStrLengthCalls($tokens) as $candidate) {
37✔
56
            [$functionNameIndex, $openParenthesisIndex, $closeParenthesisIndex] = $candidate;
37✔
57
            $arguments = $argumentsAnalyzer->getArguments($tokens, $openParenthesisIndex, $closeParenthesisIndex);
37✔
58

59
            if (1 !== \count($arguments)) {
37✔
60
                continue; // must be one argument
1✔
61
            }
62

63
            // test for leading `\` before `strlen` call
64

65
            $nextIndex = $tokens->getNextMeaningfulToken($closeParenthesisIndex);
37✔
66
            $previousIndex = $tokens->getPrevMeaningfulToken($functionNameIndex);
37✔
67

68
            if ($tokens[$previousIndex]->isGivenKind(\T_NS_SEPARATOR)) {
37✔
69
                $namespaceSeparatorIndex = $previousIndex;
5✔
70
                $previousIndex = $tokens->getPrevMeaningfulToken($previousIndex);
5✔
71
            } else {
72
                $namespaceSeparatorIndex = null;
33✔
73
            }
74

75
            // test for yoda vs non-yoda fix case
76

77
            if ($this->isOperatorOfInterest($tokens[$previousIndex])) { // test if valid yoda case to fix
37✔
78
                $operatorIndex = $previousIndex;
20✔
79
                $operandIndex = $tokens->getPrevMeaningfulToken($previousIndex);
20✔
80

81
                if (!$this->isOperandOfInterest($tokens[$operandIndex])) { // test if operand is `0` or `1`
20✔
82
                    continue;
1✔
83
                }
84

85
                $replacement = $this->getReplacementYoda($tokens[$operatorIndex], $tokens[$operandIndex]);
20✔
86

87
                if (null === $replacement) {
20✔
88
                    continue;
6✔
89
                }
90

91
                if ($this->isOfHigherPrecedence($tokens[$nextIndex])) { // is of higher precedence right; continue
14✔
92
                    continue;
2✔
93
                }
94

95
                if ($this->isOfHigherPrecedence($tokens[$tokens->getPrevMeaningfulToken($operandIndex)])) { // is of higher precedence left; continue
12✔
UNCOV
96
                    continue;
×
97
                }
98
            } elseif ($this->isOperatorOfInterest($tokens[$nextIndex])) { // test if valid !yoda case to fix
20✔
99
                $operatorIndex = $nextIndex;
20✔
100
                $operandIndex = $tokens->getNextMeaningfulToken($nextIndex);
20✔
101

102
                if (!$this->isOperandOfInterest($tokens[$operandIndex])) { // test if operand is `0` or `1`
20✔
103
                    continue;
1✔
104
                }
105

106
                $replacement = $this->getReplacementNotYoda($tokens[$operatorIndex], $tokens[$operandIndex]);
20✔
107

108
                if (null === $replacement) {
20✔
109
                    continue;
6✔
110
                }
111

112
                if ($this->isOfHigherPrecedence($tokens[$tokens->getNextMeaningfulToken($operandIndex)])) { // is of higher precedence right; continue
14✔
113
                    continue;
2✔
114
                }
115

116
                if ($this->isOfHigherPrecedence($tokens[$previousIndex])) { // is of higher precedence left; continue
12✔
UNCOV
117
                    continue;
×
118
                }
119
            } else {
120
                continue;
1✔
121
            }
122

123
            // prepare for fixing
124

125
            $keepParentheses = $this->keepParentheses($tokens, $openParenthesisIndex, $closeParenthesisIndex);
23✔
126

127
            if (\T_IS_IDENTICAL === $replacement) {
23✔
128
                $operandContent = '===';
14✔
129
            } else { // T_IS_NOT_IDENTICAL === $replacement
130
                $operandContent = '!==';
9✔
131
            }
132

133
            // apply fixing
134

135
            $tokens[$operandIndex] = new Token([\T_CONSTANT_ENCAPSED_STRING, "''"]);
23✔
136
            $tokens[$operatorIndex] = new Token([$replacement, $operandContent]);
23✔
137

138
            if (!$keepParentheses) {
23✔
139
                $tokens->clearTokenAndMergeSurroundingWhitespace($closeParenthesisIndex);
21✔
140
                $tokens->clearTokenAndMergeSurroundingWhitespace($openParenthesisIndex);
21✔
141
            }
142

143
            $tokens->clearTokenAndMergeSurroundingWhitespace($functionNameIndex);
23✔
144

145
            if (null !== $namespaceSeparatorIndex) {
23✔
146
                $tokens->clearTokenAndMergeSurroundingWhitespace($namespaceSeparatorIndex);
5✔
147
            }
148
        }
149
    }
150

151
    private function getReplacementYoda(Token $operator, Token $operand): ?int
152
    {
153
        /* Yoda 0
154

155
        0 === strlen($b) | '' === $b
156
        0 !== strlen($b) | '' !== $b
157
        0 <= strlen($b)  | X         makes no sense, assume overridden
158
        0 >= strlen($b)  | '' === $b
159
        0 < strlen($b)   | '' !== $b
160
        0 > strlen($b)   | X         makes no sense, assume overridden
161
        */
162

163
        if ('0' === $operand->getContent()) {
20✔
164
            if ($operator->isGivenKind([\T_IS_IDENTICAL, \T_IS_GREATER_OR_EQUAL])) {
14✔
165
                return \T_IS_IDENTICAL;
10✔
166
            }
167

168
            if ($operator->isGivenKind(\T_IS_NOT_IDENTICAL) || $operator->equals('<')) {
4✔
169
                return \T_IS_NOT_IDENTICAL;
2✔
170
            }
171

172
            return null;
2✔
173
        }
174

175
        /* Yoda 1
176

177
        1 === strlen($b) | X         cannot simplify
178
        1 !== strlen($b) | X         cannot simplify
179
        1 <= strlen($b)  | '' !== $b
180
        1 >= strlen($b)  |           cannot simplify
181
        1 < strlen($b)   |           cannot simplify
182
        1 > strlen($b)   | '' === $b
183
        */
184

185
        if ($operator->isGivenKind(\T_IS_SMALLER_OR_EQUAL)) {
6✔
186
            return \T_IS_NOT_IDENTICAL;
1✔
187
        }
188

189
        if ($operator->equals('>')) {
5✔
190
            return \T_IS_IDENTICAL;
1✔
191
        }
192

193
        return null;
4✔
194
    }
195

196
    private function getReplacementNotYoda(Token $operator, Token $operand): ?int
197
    {
198
        /* Not Yoda 0
199

200
        strlen($b) === 0 | $b === ''
201
        strlen($b) !== 0 | $b !== ''
202
        strlen($b) <= 0  | $b === ''
203
        strlen($b) >= 0  | X         makes no sense, assume overridden
204
        strlen($b) < 0   | X         makes no sense, assume overridden
205
        strlen($b) > 0   | $b !== ''
206
        */
207

208
        if ('0' === $operand->getContent()) {
20✔
209
            if ($operator->isGivenKind([\T_IS_IDENTICAL, \T_IS_SMALLER_OR_EQUAL])) {
10✔
210
                return \T_IS_IDENTICAL;
4✔
211
            }
212

213
            if ($operator->isGivenKind(\T_IS_NOT_IDENTICAL) || $operator->equals('>')) {
6✔
214
                return \T_IS_NOT_IDENTICAL;
4✔
215
            }
216

217
            return null;
2✔
218
        }
219

220
        /* Not Yoda 1
221

222
        strlen($b) === 1 | X         cannot simplify
223
        strlen($b) !== 1 | X         cannot simplify
224
        strlen($b) <= 1  | X         cannot simplify
225
        strlen($b) >= 1  | $b !== ''
226
        strlen($b) < 1   | $b === ''
227
        strlen($b) > 1   | X         cannot simplify
228
        */
229

230
        if ($operator->isGivenKind(\T_IS_GREATER_OR_EQUAL)) {
10✔
231
            return \T_IS_NOT_IDENTICAL;
2✔
232
        }
233

234
        if ($operator->equals('<')) {
8✔
235
            return \T_IS_IDENTICAL;
4✔
236
        }
237

238
        return null;
4✔
239
    }
240

241
    private function isOperandOfInterest(Token $token): bool
242
    {
243
        if (!$token->isGivenKind(\T_LNUMBER)) {
37✔
244
            return false;
1✔
245
        }
246

247
        $content = $token->getContent();
37✔
248

249
        return '0' === $content || '1' === $content;
37✔
250
    }
251

252
    private function isOperatorOfInterest(Token $token): bool
253
    {
254
        return
37✔
255
            $token->isGivenKind([\T_IS_IDENTICAL, \T_IS_NOT_IDENTICAL, \T_IS_SMALLER_OR_EQUAL, \T_IS_GREATER_OR_EQUAL])
37✔
256
            || $token->equals('<') || $token->equals('>');
37✔
257
    }
258

259
    private function isOfHigherPrecedence(Token $token): bool
260
    {
261
        return $token->isGivenKind([\T_INSTANCEOF, \T_POW, \T_SL, \T_SR]) || $token->equalsAny([
25✔
262
            '!',
25✔
263
            '%',
25✔
264
            '*',
25✔
265
            '+',
25✔
266
            '-',
25✔
267
            '.',
25✔
268
            '/',
25✔
269
            '~',
25✔
270
            '?',
25✔
271
        ]);
25✔
272
    }
273

274
    private function keepParentheses(Tokens $tokens, int $openParenthesisIndex, int $closeParenthesisIndex): bool
275
    {
276
        $i = $tokens->getNextMeaningfulToken($openParenthesisIndex);
23✔
277

278
        if ($tokens[$i]->isCast()) {
23✔
279
            $i = $tokens->getNextMeaningfulToken($i);
1✔
280
        }
281

282
        for (; $i < $closeParenthesisIndex; ++$i) {
23✔
283
            $token = $tokens[$i];
23✔
284

285
            if ($token->isGivenKind([\T_VARIABLE, \T_STRING]) || $token->isObjectOperator() || $token->isWhitespace() || $token->isComment()) {
23✔
286
                continue;
23✔
287
            }
288

289
            $blockType = Tokens::detectBlockType($token);
6✔
290

291
            if (null !== $blockType && $blockType['isStart']) {
6✔
292
                $i = $tokens->findBlockEnd($blockType['type'], $i);
4✔
293

294
                continue;
4✔
295
            }
296

297
            return true;
2✔
298
        }
299

300
        return false;
21✔
301
    }
302

303
    private function findStrLengthCalls(Tokens $tokens): \Generator
304
    {
305
        $candidates = [];
37✔
306
        $count = \count($tokens);
37✔
307

308
        for ($i = 0; $i < $count; ++$i) {
37✔
309
            $candidate = $this->find('strlen', $tokens, $i, $count);
37✔
310

311
            if (null === $candidate) {
37✔
312
                break;
37✔
313
            }
314

315
            $i = $candidate[1]; // proceed to openParenthesisIndex
37✔
316
            $candidates[] = $candidate;
37✔
317
        }
318

319
        foreach (array_reverse($candidates) as $candidate) {
37✔
320
            yield $candidate;
37✔
321
        }
322
    }
323
}
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