From 88be321177d478598715e896409f222c780275f9 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 16 Nov 2020 18:10:18 +0100 Subject: [PATCH] :sparkles: Add mb string sniff --- .../AlphabeticallySortedUseSniff.php | 2 +- .../Sniffs/Namespaces/UnusedUseSniff.php | 12 +++--- .../NamingConventions/ValidClassNameSniff.php | 6 +-- .../NamingConventions/ValidFileNameSniff.php | 2 +- .../NamingConventions/ValidTypeHintSniff.php | 12 +++--- .../Sniffs/PHP/EncourageMultiBytesSniff.php | 37 ++++++++++++++++ .../WhiteSpace/OpenBracketSpacingSniff.php | 2 +- .../Tests/PHP/EncourageMultiBytesUnitTest.inc | 15 +++++++ .../Tests/PHP/EncourageMultiBytesUnitTest.php | 43 +++++++++++++++++++ TwigCS/src/Report/SniffViolation.php | 2 +- TwigCS/src/Report/TextFormatter.php | 8 ++-- TwigCS/src/Sniff/AbstractSpacingSniff.php | 4 +- TwigCS/src/Token/Tokenizer.php | 22 +++++----- TwigCS/tests/TestHelper.php | 4 +- composer.json | 1 + 15 files changed, 134 insertions(+), 38 deletions(-) create mode 100644 SymfonyCustom/Sniffs/PHP/EncourageMultiBytesSniff.php create mode 100644 SymfonyCustom/Tests/PHP/EncourageMultiBytesUnitTest.inc create mode 100644 SymfonyCustom/Tests/PHP/EncourageMultiBytesUnitTest.php diff --git a/SymfonyCustom/Sniffs/Namespaces/AlphabeticallySortedUseSniff.php b/SymfonyCustom/Sniffs/Namespaces/AlphabeticallySortedUseSniff.php index 0ba3c14..08c5883 100644 --- a/SymfonyCustom/Sniffs/Namespaces/AlphabeticallySortedUseSniff.php +++ b/SymfonyCustom/Sniffs/Namespaces/AlphabeticallySortedUseSniff.php @@ -107,7 +107,7 @@ private function getUseStatements(File $phpcsFile, int $scopePtr): array $type = 'class'; if (T_STRING === $tokens[$startOfName]['code']) { - $lowerContent = strtolower($tokens[$startOfName]['content']); + $lowerContent = mb_strtolower($tokens[$startOfName]['content']); if ('function' === $lowerContent || 'const' === $lowerContent) { $type = $lowerContent; diff --git a/SymfonyCustom/Sniffs/Namespaces/UnusedUseSniff.php b/SymfonyCustom/Sniffs/Namespaces/UnusedUseSniff.php index f2ae78a..2e0162f 100644 --- a/SymfonyCustom/Sniffs/Namespaces/UnusedUseSniff.php +++ b/SymfonyCustom/Sniffs/Namespaces/UnusedUseSniff.php @@ -162,7 +162,7 @@ public function process(File $phpcsFile, $stackPtr): void $from = $phpcsFile->findNext(Tokens::$emptyTokens, $prev + 1, null, true); if ( T_STRING === $tokens[$from]['code'] - && in_array(strtolower($tokens[$from]['content']), ['const', 'function'], true) + && in_array(mb_strtolower($tokens[$from]['content']), ['const', 'function'], true) ) { $from = $phpcsFile->findNext(Tokens::$emptyTokens, $from + 1, null, true); } @@ -248,12 +248,12 @@ private function isClassUsed(File $phpcsFile, int $usePtr, int $classPtr): bool $next = $phpcsFile->findNext(Tokens::$emptyTokens, $usePtr + 1, null, true); if ( T_STRING === $tokens[$next]['code'] - && in_array(strtolower($tokens[$next]['content']), ['const', 'function'], true) + && in_array(mb_strtolower($tokens[$next]['content']), ['const', 'function'], true) ) { - $type = strtolower($tokens[$next]['content']); + $type = mb_strtolower($tokens[$next]['content']); } - $searchName = 'const' === $type ? $className : strtolower($className); + $searchName = 'const' === $type ? $className : mb_strtolower($className); $prev = $phpcsFile->findPrevious( Tokens::$emptyTokens + [ @@ -292,7 +292,7 @@ private function isClassUsed(File $phpcsFile, int $usePtr, int $classPtr): bool ); } - $useNamespace = substr($useNamespace, 0, strrpos($useNamespace, '\\') ?: 0); + $useNamespace = mb_substr($useNamespace, 0, mb_strrpos($useNamespace, '\\') ?: 0); if (false !== $namespacePtr) { $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 if ( ($isStringToken - && (('const' !== $type && strtolower($tokens[$classUsed]['content']) === $searchName) + && (('const' !== $type && mb_strtolower($tokens[$classUsed]['content']) === $searchName) || ('const' === $type && $tokens[$classUsed]['content'] === $searchName))) || ('class' === $type && ((T_DOC_COMMENT_STRING === $tokens[$classUsed]['code'] diff --git a/SymfonyCustom/Sniffs/NamingConventions/ValidClassNameSniff.php b/SymfonyCustom/Sniffs/NamingConventions/ValidClassNameSniff.php index 86716a4..67e4213 100644 --- a/SymfonyCustom/Sniffs/NamingConventions/ValidClassNameSniff.php +++ b/SymfonyCustom/Sniffs/NamingConventions/ValidClassNameSniff.php @@ -57,7 +57,7 @@ public function process(File $phpcsFile, $stackPtr): void if (T_EXTENDS === $tokens[$stackPtr]['code']) { $extend = $phpcsFile->findNext(T_STRING, $stackPtr); - if ($extend && substr($tokens[$extend]['content'], -9) === 'Exception') { + if ($extend && mb_substr($tokens[$extend]['content'], -9) === 'Exception') { $class = $phpcsFile->findPrevious(T_CLASS, $stackPtr); $name = $phpcsFile->findNext(T_STRING, $class); @@ -93,7 +93,7 @@ private function checkPrefix(File $phpcsFile, int $stackPtr, $name, string $pref { $tokens = $phpcsFile->getTokens(); - if (false !== $name && substr($tokens[$name]['content'], 0, strlen($prefix)) !== $prefix) { + if (false !== $name && mb_substr($tokens[$name]['content'], 0, mb_strlen($prefix)) !== $prefix) { $phpcsFile->addError( "$prefix name is not prefixed with '$prefix'", $stackPtr, @@ -114,7 +114,7 @@ private function checkSuffix(File $phpcsFile, int $stackPtr, $name, string $suff { $tokens = $phpcsFile->getTokens(); - if (false !== $name && substr($tokens[$name]['content'], -strlen($suffix)) !== $suffix) { + if (false !== $name && mb_substr($tokens[$name]['content'], -mb_strlen($suffix)) !== $suffix) { $phpcsFile->addError( "$suffix name is not suffixed with '$suffix'", $stackPtr, diff --git a/SymfonyCustom/Sniffs/NamingConventions/ValidFileNameSniff.php b/SymfonyCustom/Sniffs/NamingConventions/ValidFileNameSniff.php index f676997..cb16100 100644 --- a/SymfonyCustom/Sniffs/NamingConventions/ValidFileNameSniff.php +++ b/SymfonyCustom/Sniffs/NamingConventions/ValidFileNameSniff.php @@ -37,7 +37,7 @@ public function process(File $phpcsFile, $stackPtr): void $filenamePhp = basename($filename, '.php'); $filenameInc = basename($filename, '.inc'); - if (strlen($filenameInc) < strlen($filenamePhp)) { + if (mb_strlen($filenameInc) < mb_strlen($filenamePhp)) { $filename = $filenameInc; } else { $filename = $filenamePhp; diff --git a/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php b/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php index 74e83a9..d132c1b 100644 --- a/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php +++ b/SymfonyCustom/Sniffs/NamingConventions/ValidTypeHintSniff.php @@ -113,7 +113,7 @@ private function getValidTypes(string $content): string preg_match('{^'.SniffHelper::REGEX_TYPES.'$}ix', $content, $matches); if (isset($matches['array']) && '' !== $matches['array']) { - $validType = $this->getValidTypes(substr($matches['array'], 0, -2)).'[]'; + $validType = $this->getValidTypes(mb_substr($matches['array'], 0, -2)).'[]'; } elseif (isset($matches['multiple']) && '' !== $matches['multiple']) { $validType = '('.$this->getValidTypes($matches['mutipleContent']).')'; } elseif (isset($matches['generic']) && '' !== $matches['generic']) { @@ -126,8 +126,8 @@ private function getValidTypes(string $content): string $types[] = $validType; - $separators[] = substr($content, strlen($matches['type']), 1); - $content = substr($content, strlen($matches['type']) + 1); + $separators[] = mb_substr($content, mb_strlen($matches['type']), 1); + $content = mb_substr($content, mb_strlen($matches['type']) + 1); } // Remove last separator since it's an empty string @@ -185,7 +185,7 @@ private function getValidGenericType(string $genericName, string $genericContent preg_match('{^'.SniffHelper::REGEX_TYPES.',?}ix', $genericContent, $matches); $validType .= $this->getValidTypes($matches['types']).', '; - $genericContent = substr($genericContent, strlen($matches['types']) + 1); + $genericContent = mb_substr($genericContent, mb_strlen($matches['types']) + 1); } return preg_replace('/,\s$/', '>', $validType); @@ -213,7 +213,7 @@ private function getValidObjectType(string $objectContent): string preg_match('{^'.SniffHelper::REGEX_TYPES.',?}ix', $objectContent, $matches); $validType .= $this->getValidTypes($matches['types']).', '; - $objectContent = substr($objectContent, strlen($matches['types']) + 1); + $objectContent = mb_substr($objectContent, mb_strlen($matches['types']) + 1); } return preg_replace('/,\s$/', '}', $validType); @@ -226,7 +226,7 @@ private function getValidObjectType(string $objectContent): string */ private function getValidType(string $typeName): string { - $lowerType = strtolower($typeName); + $lowerType = mb_strtolower($typeName); if (isset(self::TYPES[$lowerType])) { return self::TYPES[$lowerType] ? $lowerType : $typeName; } diff --git a/SymfonyCustom/Sniffs/PHP/EncourageMultiBytesSniff.php b/SymfonyCustom/Sniffs/PHP/EncourageMultiBytesSniff.php new file mode 100644 index 0000000..29d8941 --- /dev/null +++ b/SymfonyCustom/Sniffs/PHP/EncourageMultiBytesSniff.php @@ -0,0 +1,37 @@ + 'mb_str_split', + 'stripos' => 'mb_stripos', + 'stristr' => 'mb_stristr', + 'strlen' => 'mb_strlen', + 'strpos' => 'mb_strpos', + 'strrchr' => 'mb_strrchr', + 'strripos' => 'mb_strripos', + 'strrpos' => 'mb_strrpos', + 'strstr' => 'mb_strstr', + 'strtolower' => 'mb_strtolower', + 'strtoupper' => 'mb_strtoupper', + 'substr_count' => 'mb_substr_count', + 'substr' => 'mb_substr', + ]; + + /** + * @var bool + */ + public $error = false; +} diff --git a/SymfonyCustom/Sniffs/WhiteSpace/OpenBracketSpacingSniff.php b/SymfonyCustom/Sniffs/WhiteSpace/OpenBracketSpacingSniff.php index 0b45292..09321f7 100644 --- a/SymfonyCustom/Sniffs/WhiteSpace/OpenBracketSpacingSniff.php +++ b/SymfonyCustom/Sniffs/WhiteSpace/OpenBracketSpacingSniff.php @@ -34,7 +34,7 @@ public function process(File $phpcsFile, $stackPtr): void if ( isset($tokens[$stackPtr + 1]) && T_WHITESPACE === $tokens[$stackPtr + 1]['code'] - && false === strpos($tokens[$stackPtr + 1]['content'], $phpcsFile->eolChar) + && false === mb_strpos($tokens[$stackPtr + 1]['content'], $phpcsFile->eolChar) ) { $error = 'There should be no space after an opening "%s"'; $fix = $phpcsFile->addFixableError( diff --git a/SymfonyCustom/Tests/PHP/EncourageMultiBytesUnitTest.inc b/SymfonyCustom/Tests/PHP/EncourageMultiBytesUnitTest.inc new file mode 100644 index 0000000..1ec7a93 --- /dev/null +++ b/SymfonyCustom/Tests/PHP/EncourageMultiBytesUnitTest.inc @@ -0,0 +1,15 @@ + 1, + 4 => 1, + 5 => 1, + 6 => 1, + 7 => 1, + 8 => 1, + 9 => 1, + 10 => 1, + 11 => 1, + 12 => 1, + 13 => 1, + 14 => 1, + 15 => 1, + ]; + } +} diff --git a/TwigCS/src/Report/SniffViolation.php b/TwigCS/src/Report/SniffViolation.php index 01b3d69..739341f 100644 --- a/TwigCS/src/Report/SniffViolation.php +++ b/TwigCS/src/Report/SniffViolation.php @@ -97,7 +97,7 @@ public function getLevelAsString(): string */ public static function getLevelAsInt(string $level): int { - switch (strtoupper($level)) { + switch (mb_strtoupper($level)) { case self::LEVEL_NOTICE: return Report::MESSAGE_TYPE_NOTICE; case self::LEVEL_WARNING: diff --git a/TwigCS/src/Report/TextFormatter.php b/TwigCS/src/Report/TextFormatter.php index f2c2f28..1dd54af 100644 --- a/TwigCS/src/Report/TextFormatter.php +++ b/TwigCS/src/Report/TextFormatter.php @@ -122,11 +122,11 @@ protected function getContext(string $template, ?int $line, int $context): array while ($position < $max) { if (preg_match('/^([\s\t]+)/', $lines[$position], $match)) { if (null === $indentCount) { - $indentCount = strlen($match[1]); + $indentCount = mb_strlen($match[1]); } - if (strlen($match[1]) < $indentCount) { - $indentCount = strlen($match[1]); + if (mb_strlen($match[1]) < $indentCount) { + $indentCount = mb_strlen($match[1]); } } else { $indentCount = 0; @@ -137,7 +137,7 @@ protected function getContext(string $template, ?int $line, int $context): array } foreach ($result as $index => $code) { - $result[$index] = substr($code, $indentCount); + $result[$index] = mb_substr($code, $indentCount); } return $result; diff --git a/TwigCS/src/Sniff/AbstractSpacingSniff.php b/TwigCS/src/Sniff/AbstractSpacingSniff.php index 1d4f2bf..007654f 100644 --- a/TwigCS/src/Sniff/AbstractSpacingSniff.php +++ b/TwigCS/src/Sniff/AbstractSpacingSniff.php @@ -70,7 +70,7 @@ private function checkSpaceAfter(int $tokenPosition, array $tokens, int $expecte } if ($this->isTokenMatching($tokens[$tokenPosition + 1], Token::WHITESPACE_TOKENS)) { - $count = strlen($tokens[$tokenPosition + 1]->getValue()); + $count = mb_strlen($tokens[$tokenPosition + 1]->getValue()); } else { $count = 0; } @@ -111,7 +111,7 @@ private function checkSpaceBefore(int $tokenPosition, array $tokens, int $expect } if ($this->isTokenMatching($tokens[$tokenPosition - 1], Token::WHITESPACE_TOKENS)) { - $count = strlen($tokens[$tokenPosition - 1]->getValue()); + $count = mb_strlen($tokens[$tokenPosition - 1]->getValue()); } else { $count = 0; } diff --git a/TwigCS/src/Token/Tokenizer.php b/TwigCS/src/Token/Tokenizer.php index 5fc528a..1898418 100644 --- a/TwigCS/src/Token/Tokenizer.php +++ b/TwigCS/src/Token/Tokenizer.php @@ -193,7 +193,7 @@ protected function resetState(Source $source): void $this->bracketsAndTernary = []; $this->code = str_replace(["\r\n", "\r"], "\n", $source->getCode()); - $this->end = strlen($this->code); + $this->end = mb_strlen($this->code); $this->filename = $source->getName(); } @@ -310,8 +310,8 @@ protected function moveCurrentPosition(int $value = 1): void */ protected function moveCursor(string $value): void { - $this->cursor += strlen($value); - $this->line += substr_count($value, "\n"); + $this->cursor += mb_strlen($value); + $this->line += mb_substr_count($value, "\n"); } /** @@ -322,7 +322,7 @@ protected function moveCursor(string $value): void */ protected function pushToken(int $type, string $value = null): void { - $tokenPositionInLine = $this->cursor - strrpos(substr($this->code, 0, $this->cursor), PHP_EOL); + $tokenPositionInLine = $this->cursor - mb_strrpos(mb_substr($this->code, 0, $this->cursor), PHP_EOL); $this->tokens[] = new Token($type, $this->line, $tokenPositionInLine, $this->filename, $value); } @@ -350,7 +350,7 @@ protected function lexExpression(): void $this->lexName($match[0]); } elseif (preg_match(self::REGEX_NUMBER, $this->code, $match, 0, $this->cursor)) { $this->lexNumber($match[0]); - } elseif (false !== strpos(self::PUNCTUATION, $this->code[$this->cursor])) { + } elseif (false !== mb_strpos(self::PUNCTUATION, $this->code[$this->cursor])) { $this->lexPunctuation(); } elseif (preg_match(self::REGEX_STRING, $this->code, $match, 0, $this->cursor)) { $this->lexString($match[0]); @@ -436,7 +436,7 @@ protected function lexDqString(): void $this->lexStartInterpolation(); } elseif ( preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, 0, $this->cursor) - && strlen($match[0]) > 0 + && mb_strlen($match[0]) > 0 ) { $this->pushToken(Token::STRING_TYPE, $match[0]); $this->moveCursor($match[0]); @@ -500,8 +500,8 @@ protected function lexData(int $limit = 0): void $value = $match[0]; // Stop if cursor reaches the next token start. - if (0 !== $limit && $limit <= ($this->cursor + strlen($value))) { - $value = substr($value, 0, $limit - $this->cursor); + if (0 !== $limit && $limit <= ($this->cursor + mb_strlen($value))) { + $value = mb_substr($value, 0, $limit - $this->cursor); } // Fixing token start among expressions and comments. @@ -702,7 +702,7 @@ protected function lexPunctuation(): void return; } - if (false !== strpos(',)]}', $currentToken)) { + if (false !== mb_strpos(',)]}', $currentToken)) { // Because {{ foo ? 'yes' }} is the same as {{ foo ? 'yes' : '' }} do { array_pop($this->bracketsAndTernary); @@ -716,9 +716,9 @@ protected function lexPunctuation(): void } } - if (false !== strpos('([{', $currentToken)) { + if (false !== mb_strpos('([{', $currentToken)) { $this->bracketsAndTernary[] = [$currentToken, $this->line]; - } elseif (false !== strpos(')]}', $currentToken)) { + } elseif (false !== mb_strpos(')]}', $currentToken)) { if (0 === count($this->bracketsAndTernary)) { throw new Exception(sprintf('Unexpected "%s"', $currentToken)); } diff --git a/TwigCS/tests/TestHelper.php b/TwigCS/tests/TestHelper.php index c20167f..0912427 100644 --- a/TwigCS/tests/TestHelper.php +++ b/TwigCS/tests/TestHelper.php @@ -25,8 +25,8 @@ private function __construct() public static function generateDiff(string $contents, string $filePath): string { $cwd = getcwd().DIRECTORY_SEPARATOR; - if (strpos($filePath, $cwd) === 0) { - $filename = substr($filePath, strlen($cwd)); + if (mb_strpos($filePath, $cwd) === 0) { + $filename = mb_substr($filePath, mb_strlen($cwd)); } else { $filename = $filePath; } diff --git a/composer.json b/composer.json index 7c8adbd..9bdb430 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,7 @@ "bin": ["bin/twigcs"], "require": { "php": ">=7.2", + "ext-mbstring": "*", "squizlabs/php_codesniffer": "^3.5", "symfony/console": "^4.4 || ^5.0", "symfony/twig-bridge": "^4.4 || ^5.0",