• 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.0
/src/Fixer/Alias/MbStrFunctionsFixer.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\Alias;
16

17
use PhpCsFixer\AbstractFixer;
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\Analyzer\FunctionsAnalyzer;
23
use PhpCsFixer\Tokenizer\CT;
24
use PhpCsFixer\Tokenizer\Token;
25
use PhpCsFixer\Tokenizer\Tokens;
26

27
/**
28
 * @author Filippo Tessarotto <zoeslam@gmail.com>
29
 *
30
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
31
 */
32
final class MbStrFunctionsFixer extends AbstractFixer
33
{
34
    /**
35
     * list of the string-related function names and their mb_ equivalent.
36
     *
37
     * @var array<
38
     *     string,
39
     *     array{
40
     *         alternativeName: string,
41
     *         argumentCount: list<int>,
42
     *     },
43
     * >
44
     */
45
    private static array $functionsMap = [
46
        'str_split' => ['alternativeName' => 'mb_str_split', 'argumentCount' => [1, 2, 3]],
47
        'stripos' => ['alternativeName' => 'mb_stripos', 'argumentCount' => [2, 3]],
48
        'stristr' => ['alternativeName' => 'mb_stristr', 'argumentCount' => [2, 3]],
49
        'strlen' => ['alternativeName' => 'mb_strlen', 'argumentCount' => [1]],
50
        'strpos' => ['alternativeName' => 'mb_strpos', 'argumentCount' => [2, 3]],
51
        'strrchr' => ['alternativeName' => 'mb_strrchr', 'argumentCount' => [2]],
52
        'strripos' => ['alternativeName' => 'mb_strripos', 'argumentCount' => [2, 3]],
53
        'strrpos' => ['alternativeName' => 'mb_strrpos', 'argumentCount' => [2, 3]],
54
        'strstr' => ['alternativeName' => 'mb_strstr', 'argumentCount' => [2, 3]],
55
        'strtolower' => ['alternativeName' => 'mb_strtolower', 'argumentCount' => [1]],
56
        'strtoupper' => ['alternativeName' => 'mb_strtoupper', 'argumentCount' => [1]],
57
        'substr' => ['alternativeName' => 'mb_substr', 'argumentCount' => [2, 3]],
58
        'substr_count' => ['alternativeName' => 'mb_substr_count', 'argumentCount' => [2, 3, 4]],
59
    ];
60

61
    /**
62
     * @var array<
63
     *     string,
64
     *     array{
65
     *         alternativeName: string,
66
     *         argumentCount: list<int>,
67
     *     },
68
     * >
69
     */
70
    private array $functions;
71

72
    public function __construct()
73
    {
74
        parent::__construct();
33✔
75

76
        if (\PHP_VERSION_ID >= 8_03_00) {
33✔
77
            self::$functionsMap['str_pad'] = ['alternativeName' => 'mb_str_pad', 'argumentCount' => [1, 2, 3, 4]];
33✔
78
        }
79

80
        if (\PHP_VERSION_ID >= 8_04_00) {
33✔
81
            self::$functionsMap['trim'] = ['alternativeName' => 'mb_trim', 'argumentCount' => [1, 2]];
33✔
82
            self::$functionsMap['ltrim'] = ['alternativeName' => 'mb_ltrim', 'argumentCount' => [1, 2]];
33✔
83
            self::$functionsMap['rtrim'] = ['alternativeName' => 'mb_rtrim', 'argumentCount' => [1, 2]];
33✔
84
        }
85

86
        $this->functions = array_filter(
33✔
87
            self::$functionsMap,
33✔
88
            static fn (array $mapping): bool => (new \ReflectionFunction($mapping['alternativeName']))->isInternal()
33✔
89
        );
33✔
90
    }
91

92
    public function isCandidate(Tokens $tokens): bool
93
    {
94
        return $tokens->isTokenKindFound(\T_STRING);
24✔
95
    }
96

97
    public function isRisky(): bool
98
    {
99
        return true;
1✔
100
    }
101

102
    /**
103
     * {@inheritdoc}
104
     *
105
     * Must run before NativeFunctionInvocationFixer.
106
     */
107
    public function getPriority(): int
108
    {
109
        return 2;
1✔
110
    }
111

112
    public function getDefinition(): FixerDefinitionInterface
113
    {
114
        return new FixerDefinition(
3✔
115
            'Replace non multibyte-safe functions with corresponding mb function.',
3✔
116
            [
3✔
117
                new CodeSample(
3✔
118
                    <<<'PHP'
3✔
119
                        <?php
120
                        $a = strlen($a);
121
                        $a = strpos($a, $b);
122
                        $a = strrpos($a, $b);
123
                        $a = substr($a, $b);
124
                        $a = strtolower($a);
125
                        $a = strtoupper($a);
126
                        $a = stripos($a, $b);
127
                        $a = strripos($a, $b);
128
                        $a = strstr($a, $b);
129
                        $a = stristr($a, $b);
130
                        $a = strrchr($a, $b);
131
                        $a = substr_count($a, $b);
132

133
                        PHP
3✔
134
                ),
3✔
135
            ],
3✔
136
            null,
3✔
137
            'Risky when any of the functions are overridden, or when relying on the string byte size rather than its length in characters.'
3✔
138
        );
3✔
139
    }
140

141
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
142
    {
143
        $argumentsAnalyzer = new ArgumentsAnalyzer();
23✔
144
        $functionsAnalyzer = new FunctionsAnalyzer();
23✔
145

146
        for ($index = $tokens->count() - 1; $index > 0; --$index) {
23✔
147
            if (!$tokens[$index]->isGivenKind(\T_STRING)) {
23✔
148
                continue;
23✔
149
            }
150

151
            $lowercasedContent = strtolower($tokens[$index]->getContent());
23✔
152
            if (!isset($this->functions[$lowercasedContent])) {
23✔
153
                continue;
17✔
154
            }
155

156
            // is it a global function call?
157
            if ($functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) {
23✔
158
                $openParenthesis = $tokens->getNextMeaningfulToken($index);
15✔
159
                $closeParenthesis = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis);
15✔
160
                $numberOfArguments = $argumentsAnalyzer->countArguments($tokens, $openParenthesis, $closeParenthesis);
15✔
161
                if (!\in_array($numberOfArguments, $this->functions[$lowercasedContent]['argumentCount'], true)) {
15✔
162
                    continue;
2✔
163
                }
164
                $tokens[$index] = new Token([\T_STRING, $this->functions[$lowercasedContent]['alternativeName']]);
13✔
165

166
                continue;
13✔
167
            }
168

169
            // is it a global function import?
170
            $functionIndex = $tokens->getPrevMeaningfulToken($index);
9✔
171
            if ($tokens[$functionIndex]->isGivenKind(\T_NS_SEPARATOR)) {
9✔
172
                $functionIndex = $tokens->getPrevMeaningfulToken($functionIndex);
4✔
173
            }
174
            if (!$tokens[$functionIndex]->isGivenKind(CT::T_FUNCTION_IMPORT)) {
9✔
175
                continue;
9✔
176
            }
177
            $useIndex = $tokens->getPrevMeaningfulToken($functionIndex);
1✔
178
            if (!$tokens[$useIndex]->isGivenKind(\T_USE)) {
1✔
UNCOV
179
                continue;
×
180
            }
181
            $tokens[$index] = new Token([\T_STRING, $this->functions[$lowercasedContent]['alternativeName']]);
1✔
182
        }
183
    }
184
}
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