Skip to content

Commit 88be321

Browse files
✨ Add mb string sniff
1 parent 5596f47 commit 88be321

15 files changed

+134
-38
lines changed

SymfonyCustom/Sniffs/Namespaces/AlphabeticallySortedUseSniff.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ private function getUseStatements(File $phpcsFile, int $scopePtr): array
107107

108108
$type = 'class';
109109
if (T_STRING === $tokens[$startOfName]['code']) {
110-
$lowerContent = strtolower($tokens[$startOfName]['content']);
110+
$lowerContent = mb_strtolower($tokens[$startOfName]['content']);
111111
if ('function' === $lowerContent || 'const' === $lowerContent) {
112112
$type = $lowerContent;
113113

SymfonyCustom/Sniffs/Namespaces/UnusedUseSniff.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ public function process(File $phpcsFile, $stackPtr): void
162162
$from = $phpcsFile->findNext(Tokens::$emptyTokens, $prev + 1, null, true);
163163
if (
164164
T_STRING === $tokens[$from]['code']
165-
&& in_array(strtolower($tokens[$from]['content']), ['const', 'function'], true)
165+
&& in_array(mb_strtolower($tokens[$from]['content']), ['const', 'function'], true)
166166
) {
167167
$from = $phpcsFile->findNext(Tokens::$emptyTokens, $from + 1, null, true);
168168
}
@@ -248,12 +248,12 @@ private function isClassUsed(File $phpcsFile, int $usePtr, int $classPtr): bool
248248
$next = $phpcsFile->findNext(Tokens::$emptyTokens, $usePtr + 1, null, true);
249249
if (
250250
T_STRING === $tokens[$next]['code']
251-
&& in_array(strtolower($tokens[$next]['content']), ['const', 'function'], true)
251+
&& in_array(mb_strtolower($tokens[$next]['content']), ['const', 'function'], true)
252252
) {
253-
$type = strtolower($tokens[$next]['content']);
253+
$type = mb_strtolower($tokens[$next]['content']);
254254
}
255255

256-
$searchName = 'const' === $type ? $className : strtolower($className);
256+
$searchName = 'const' === $type ? $className : mb_strtolower($className);
257257

258258
$prev = $phpcsFile->findPrevious(
259259
Tokens::$emptyTokens + [
@@ -292,7 +292,7 @@ private function isClassUsed(File $phpcsFile, int $usePtr, int $classPtr): bool
292292
);
293293
}
294294

295-
$useNamespace = substr($useNamespace, 0, strrpos($useNamespace, '\\') ?: 0);
295+
$useNamespace = mb_substr($useNamespace, 0, mb_strrpos($useNamespace, '\\') ?: 0);
296296

297297
if (false !== $namespacePtr) {
298298
$namespace = $this->getNamespace($phpcsFile, $namespacePtr + 1, [T_CURLY_OPEN, T_SEMICOLON]);
@@ -315,7 +315,7 @@ private function isClassUsed(File $phpcsFile, int $usePtr, int $classPtr): bool
315315

316316
if (
317317
($isStringToken
318-
&& (('const' !== $type && strtolower($tokens[$classUsed]['content']) === $searchName)
318+
&& (('const' !== $type && mb_strtolower($tokens[$classUsed]['content']) === $searchName)
319319
|| ('const' === $type && $tokens[$classUsed]['content'] === $searchName)))
320320
|| ('class' === $type
321321
&& ((T_DOC_COMMENT_STRING === $tokens[$classUsed]['code']

SymfonyCustom/Sniffs/NamingConventions/ValidClassNameSniff.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public function process(File $phpcsFile, $stackPtr): void
5757
if (T_EXTENDS === $tokens[$stackPtr]['code']) {
5858
$extend = $phpcsFile->findNext(T_STRING, $stackPtr);
5959

60-
if ($extend && substr($tokens[$extend]['content'], -9) === 'Exception') {
60+
if ($extend && mb_substr($tokens[$extend]['content'], -9) === 'Exception') {
6161
$class = $phpcsFile->findPrevious(T_CLASS, $stackPtr);
6262
$name = $phpcsFile->findNext(T_STRING, $class);
6363

@@ -93,7 +93,7 @@ private function checkPrefix(File $phpcsFile, int $stackPtr, $name, string $pref
9393
{
9494
$tokens = $phpcsFile->getTokens();
9595

96-
if (false !== $name && substr($tokens[$name]['content'], 0, strlen($prefix)) !== $prefix) {
96+
if (false !== $name && mb_substr($tokens[$name]['content'], 0, mb_strlen($prefix)) !== $prefix) {
9797
$phpcsFile->addError(
9898
"$prefix name is not prefixed with '$prefix'",
9999
$stackPtr,
@@ -114,7 +114,7 @@ private function checkSuffix(File $phpcsFile, int $stackPtr, $name, string $suff
114114
{
115115
$tokens = $phpcsFile->getTokens();
116116

117-
if (false !== $name && substr($tokens[$name]['content'], -strlen($suffix)) !== $suffix) {
117+
if (false !== $name && mb_substr($tokens[$name]['content'], -mb_strlen($suffix)) !== $suffix) {
118118
$phpcsFile->addError(
119119
"$suffix name is not suffixed with '$suffix'",
120120
$stackPtr,

SymfonyCustom/Sniffs/NamingConventions/ValidFileNameSniff.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public function process(File $phpcsFile, $stackPtr): void
3737
$filenamePhp = basename($filename, '.php');
3838
$filenameInc = basename($filename, '.inc');
3939

40-
if (strlen($filenameInc) < strlen($filenamePhp)) {
40+
if (mb_strlen($filenameInc) < mb_strlen($filenamePhp)) {
4141
$filename = $filenameInc;
4242
} else {
4343
$filename = $filenamePhp;

SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ private function getValidTypes(string $content): string
113113
preg_match('{^'.SniffHelper::REGEX_TYPES.'$}ix', $content, $matches);
114114

115115
if (isset($matches['array']) && '' !== $matches['array']) {
116-
$validType = $this->getValidTypes(substr($matches['array'], 0, -2)).'[]';
116+
$validType = $this->getValidTypes(mb_substr($matches['array'], 0, -2)).'[]';
117117
} elseif (isset($matches['multiple']) && '' !== $matches['multiple']) {
118118
$validType = '('.$this->getValidTypes($matches['mutipleContent']).')';
119119
} elseif (isset($matches['generic']) && '' !== $matches['generic']) {
@@ -126,8 +126,8 @@ private function getValidTypes(string $content): string
126126

127127
$types[] = $validType;
128128

129-
$separators[] = substr($content, strlen($matches['type']), 1);
130-
$content = substr($content, strlen($matches['type']) + 1);
129+
$separators[] = mb_substr($content, mb_strlen($matches['type']), 1);
130+
$content = mb_substr($content, mb_strlen($matches['type']) + 1);
131131
}
132132

133133
// Remove last separator since it's an empty string
@@ -185,7 +185,7 @@ private function getValidGenericType(string $genericName, string $genericContent
185185
preg_match('{^'.SniffHelper::REGEX_TYPES.',?}ix', $genericContent, $matches);
186186

187187
$validType .= $this->getValidTypes($matches['types']).', ';
188-
$genericContent = substr($genericContent, strlen($matches['types']) + 1);
188+
$genericContent = mb_substr($genericContent, mb_strlen($matches['types']) + 1);
189189
}
190190

191191
return preg_replace('/,\s$/', '>', $validType);
@@ -213,7 +213,7 @@ private function getValidObjectType(string $objectContent): string
213213
preg_match('{^'.SniffHelper::REGEX_TYPES.',?}ix', $objectContent, $matches);
214214

215215
$validType .= $this->getValidTypes($matches['types']).', ';
216-
$objectContent = substr($objectContent, strlen($matches['types']) + 1);
216+
$objectContent = mb_substr($objectContent, mb_strlen($matches['types']) + 1);
217217
}
218218

219219
return preg_replace('/,\s$/', '}', $validType);
@@ -226,7 +226,7 @@ private function getValidObjectType(string $objectContent): string
226226
*/
227227
private function getValidType(string $typeName): string
228228
{
229-
$lowerType = strtolower($typeName);
229+
$lowerType = mb_strtolower($typeName);
230230
if (isset(self::TYPES[$lowerType])) {
231231
return self::TYPES[$lowerType] ? $lowerType : $typeName;
232232
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SymfonyCustom\Sniffs\PHP;
6+
7+
use PHP_CodeSniffer\Standards\Generic\Sniffs\PHP\ForbiddenFunctionsSniff;
8+
9+
/**
10+
* Class EncourageMultiBytesSniff
11+
*/
12+
class EncourageMultiBytesSniff extends ForbiddenFunctionsSniff
13+
{
14+
/**
15+
* @var array
16+
*/
17+
public $forbiddenFunctions = [
18+
'str_split' => 'mb_str_split',
19+
'stripos' => 'mb_stripos',
20+
'stristr' => 'mb_stristr',
21+
'strlen' => 'mb_strlen',
22+
'strpos' => 'mb_strpos',
23+
'strrchr' => 'mb_strrchr',
24+
'strripos' => 'mb_strripos',
25+
'strrpos' => 'mb_strrpos',
26+
'strstr' => 'mb_strstr',
27+
'strtolower' => 'mb_strtolower',
28+
'strtoupper' => 'mb_strtoupper',
29+
'substr_count' => 'mb_substr_count',
30+
'substr' => 'mb_substr',
31+
];
32+
33+
/**
34+
* @var bool
35+
*/
36+
public $error = false;
37+
}

SymfonyCustom/Sniffs/WhiteSpace/OpenBracketSpacingSniff.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public function process(File $phpcsFile, $stackPtr): void
3434
if (
3535
isset($tokens[$stackPtr + 1])
3636
&& T_WHITESPACE === $tokens[$stackPtr + 1]['code']
37-
&& false === strpos($tokens[$stackPtr + 1]['content'], $phpcsFile->eolChar)
37+
&& false === mb_strpos($tokens[$stackPtr + 1]['content'], $phpcsFile->eolChar)
3838
) {
3939
$error = 'There should be no space after an opening "%s"';
4040
$fix = $phpcsFile->addFixableError(
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
str_split('e');
4+
stripos('eee', 'e');
5+
stristr('eee', 'e');
6+
strlen('eee');
7+
strpos('eee', 'e');
8+
strrchr('eee', 'e');
9+
strripos('eee', 'e');
10+
strrpos('eee', 'e');
11+
strstr('eee', 'e');
12+
strtolower('eee');
13+
strtoupper('eee');
14+
substr_count('eee', 'e');
15+
substr('eee', 1);
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SymfonyCustom\Tests\PHP;
6+
7+
use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;
8+
9+
/**
10+
* Unit test class for the EncourageMultiBytes sniff.
11+
*/
12+
class EncourageMultiBytesUnitTest extends AbstractSniffUnitTest
13+
{
14+
/**
15+
* @return array
16+
*/
17+
protected function getErrorList(): array
18+
{
19+
return [];
20+
}
21+
22+
/**
23+
* @return array
24+
*/
25+
protected function getWarningList(): array
26+
{
27+
return [
28+
3 => 1,
29+
4 => 1,
30+
5 => 1,
31+
6 => 1,
32+
7 => 1,
33+
8 => 1,
34+
9 => 1,
35+
10 => 1,
36+
11 => 1,
37+
12 => 1,
38+
13 => 1,
39+
14 => 1,
40+
15 => 1,
41+
];
42+
}
43+
}

TwigCS/src/Report/SniffViolation.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public function getLevelAsString(): string
9797
*/
9898
public static function getLevelAsInt(string $level): int
9999
{
100-
switch (strtoupper($level)) {
100+
switch (mb_strtoupper($level)) {
101101
case self::LEVEL_NOTICE:
102102
return Report::MESSAGE_TYPE_NOTICE;
103103
case self::LEVEL_WARNING:

TwigCS/src/Report/TextFormatter.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,11 @@ protected function getContext(string $template, ?int $line, int $context): array
122122
while ($position < $max) {
123123
if (preg_match('/^([\s\t]+)/', $lines[$position], $match)) {
124124
if (null === $indentCount) {
125-
$indentCount = strlen($match[1]);
125+
$indentCount = mb_strlen($match[1]);
126126
}
127127

128-
if (strlen($match[1]) < $indentCount) {
129-
$indentCount = strlen($match[1]);
128+
if (mb_strlen($match[1]) < $indentCount) {
129+
$indentCount = mb_strlen($match[1]);
130130
}
131131
} else {
132132
$indentCount = 0;
@@ -137,7 +137,7 @@ protected function getContext(string $template, ?int $line, int $context): array
137137
}
138138

139139
foreach ($result as $index => $code) {
140-
$result[$index] = substr($code, $indentCount);
140+
$result[$index] = mb_substr($code, $indentCount);
141141
}
142142

143143
return $result;

TwigCS/src/Sniff/AbstractSpacingSniff.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ private function checkSpaceAfter(int $tokenPosition, array $tokens, int $expecte
7070
}
7171

7272
if ($this->isTokenMatching($tokens[$tokenPosition + 1], Token::WHITESPACE_TOKENS)) {
73-
$count = strlen($tokens[$tokenPosition + 1]->getValue());
73+
$count = mb_strlen($tokens[$tokenPosition + 1]->getValue());
7474
} else {
7575
$count = 0;
7676
}
@@ -111,7 +111,7 @@ private function checkSpaceBefore(int $tokenPosition, array $tokens, int $expect
111111
}
112112

113113
if ($this->isTokenMatching($tokens[$tokenPosition - 1], Token::WHITESPACE_TOKENS)) {
114-
$count = strlen($tokens[$tokenPosition - 1]->getValue());
114+
$count = mb_strlen($tokens[$tokenPosition - 1]->getValue());
115115
} else {
116116
$count = 0;
117117
}

TwigCS/src/Token/Tokenizer.php

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ protected function resetState(Source $source): void
193193
$this->bracketsAndTernary = [];
194194

195195
$this->code = str_replace(["\r\n", "\r"], "\n", $source->getCode());
196-
$this->end = strlen($this->code);
196+
$this->end = mb_strlen($this->code);
197197
$this->filename = $source->getName();
198198
}
199199

@@ -310,8 +310,8 @@ protected function moveCurrentPosition(int $value = 1): void
310310
*/
311311
protected function moveCursor(string $value): void
312312
{
313-
$this->cursor += strlen($value);
314-
$this->line += substr_count($value, "\n");
313+
$this->cursor += mb_strlen($value);
314+
$this->line += mb_substr_count($value, "\n");
315315
}
316316

317317
/**
@@ -322,7 +322,7 @@ protected function moveCursor(string $value): void
322322
*/
323323
protected function pushToken(int $type, string $value = null): void
324324
{
325-
$tokenPositionInLine = $this->cursor - strrpos(substr($this->code, 0, $this->cursor), PHP_EOL);
325+
$tokenPositionInLine = $this->cursor - mb_strrpos(mb_substr($this->code, 0, $this->cursor), PHP_EOL);
326326
$this->tokens[] = new Token($type, $this->line, $tokenPositionInLine, $this->filename, $value);
327327
}
328328

@@ -350,7 +350,7 @@ protected function lexExpression(): void
350350
$this->lexName($match[0]);
351351
} elseif (preg_match(self::REGEX_NUMBER, $this->code, $match, 0, $this->cursor)) {
352352
$this->lexNumber($match[0]);
353-
} elseif (false !== strpos(self::PUNCTUATION, $this->code[$this->cursor])) {
353+
} elseif (false !== mb_strpos(self::PUNCTUATION, $this->code[$this->cursor])) {
354354
$this->lexPunctuation();
355355
} elseif (preg_match(self::REGEX_STRING, $this->code, $match, 0, $this->cursor)) {
356356
$this->lexString($match[0]);
@@ -436,7 +436,7 @@ protected function lexDqString(): void
436436
$this->lexStartInterpolation();
437437
} elseif (
438438
preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, 0, $this->cursor)
439-
&& strlen($match[0]) > 0
439+
&& mb_strlen($match[0]) > 0
440440
) {
441441
$this->pushToken(Token::STRING_TYPE, $match[0]);
442442
$this->moveCursor($match[0]);
@@ -500,8 +500,8 @@ protected function lexData(int $limit = 0): void
500500
$value = $match[0];
501501

502502
// Stop if cursor reaches the next token start.
503-
if (0 !== $limit && $limit <= ($this->cursor + strlen($value))) {
504-
$value = substr($value, 0, $limit - $this->cursor);
503+
if (0 !== $limit && $limit <= ($this->cursor + mb_strlen($value))) {
504+
$value = mb_substr($value, 0, $limit - $this->cursor);
505505
}
506506

507507
// Fixing token start among expressions and comments.
@@ -702,7 +702,7 @@ protected function lexPunctuation(): void
702702

703703
return;
704704
}
705-
if (false !== strpos(',)]}', $currentToken)) {
705+
if (false !== mb_strpos(',)]}', $currentToken)) {
706706
// Because {{ foo ? 'yes' }} is the same as {{ foo ? 'yes' : '' }}
707707
do {
708708
array_pop($this->bracketsAndTernary);
@@ -716,9 +716,9 @@ protected function lexPunctuation(): void
716716
}
717717
}
718718

719-
if (false !== strpos('([{', $currentToken)) {
719+
if (false !== mb_strpos('([{', $currentToken)) {
720720
$this->bracketsAndTernary[] = [$currentToken, $this->line];
721-
} elseif (false !== strpos(')]}', $currentToken)) {
721+
} elseif (false !== mb_strpos(')]}', $currentToken)) {
722722
if (0 === count($this->bracketsAndTernary)) {
723723
throw new Exception(sprintf('Unexpected "%s"', $currentToken));
724724
}

TwigCS/tests/TestHelper.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ private function __construct()
2525
public static function generateDiff(string $contents, string $filePath): string
2626
{
2727
$cwd = getcwd().DIRECTORY_SEPARATOR;
28-
if (strpos($filePath, $cwd) === 0) {
29-
$filename = substr($filePath, strlen($cwd));
28+
if (mb_strpos($filePath, $cwd) === 0) {
29+
$filename = mb_substr($filePath, mb_strlen($cwd));
3030
} else {
3131
$filename = $filePath;
3232
}

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"bin": ["bin/twigcs"],
1919
"require": {
2020
"php": ">=7.2",
21+
"ext-mbstring": "*",
2122
"squizlabs/php_codesniffer": "^3.5",
2223
"symfony/console": "^4.4 || ^5.0",
2324
"symfony/twig-bridge": "^4.4 || ^5.0",

0 commit comments

Comments
 (0)