diff --git a/src/Psalm/CodeLocation.php b/src/Psalm/CodeLocation.php index 588a83230e5..90c612bacf8 100644 --- a/src/Psalm/CodeLocation.php +++ b/src/Psalm/CodeLocation.php @@ -14,8 +14,8 @@ use function strlen; use function strpos; use function strrpos; -use function substr; use function substr_count; +use function mb_strcut; use function trim; class CodeLocation @@ -183,7 +183,7 @@ private function calculateRealLocation(): void ) { $preview_lines = explode( "\n", - substr( + mb_strcut( $file_contents, $this->preview_start, $this->selection_start - $this->preview_start - 1 @@ -206,7 +206,7 @@ private function calculateRealLocation(): void $indentation = (int)strpos($key_line, '@'); - $key_line = trim(preg_replace('@\**/\s*@', '', substr($key_line, $indentation))); + $key_line = trim(preg_replace('@\**/\s*@', '', mb_strcut($key_line, $indentation))); $this->selection_start = $preview_offset + $indentation + $this->preview_start; $this->selection_end = $this->selection_start + strlen($key_line); @@ -258,7 +258,7 @@ private function calculateRealLocation(): void throw new \UnexpectedValueException('Unrecognised regex type ' . $this->regex_type); } - $preview_snippet = substr( + $preview_snippet = mb_strcut( $file_contents, $this->selection_start, $this->selection_end - $this->selection_start @@ -298,8 +298,8 @@ private function calculateRealLocation(): void } } - $this->snippet = substr($file_contents, $this->preview_start, $this->preview_end - $this->preview_start); - $this->text = substr($file_contents, $this->selection_start, $this->selection_end - $this->selection_start); + $this->snippet = mb_strcut($file_contents, $this->preview_start, $this->preview_end - $this->preview_start); + $this->text = mb_strcut($file_contents, $this->selection_start, $this->selection_end - $this->selection_start); // reset preview start to beginning of line $this->column_from = $this->selection_start - diff --git a/src/Psalm/Internal/Json/Json.php b/src/Psalm/Internal/Json/Json.php index b62130a0f8c..66ec0cb8a54 100644 --- a/src/Psalm/Internal/Json/Json.php +++ b/src/Psalm/Internal/Json/Json.php @@ -4,6 +4,7 @@ use RuntimeException; use function json_encode; +use function json_last_error_msg; use const JSON_PRETTY_PRINT; use const JSON_UNESCAPED_SLASHES; use const JSON_UNESCAPED_UNICODE; @@ -34,7 +35,8 @@ public static function encode($data, ?int $options = null): string $result = json_encode($data, $options); if ($result === false) { - throw new RuntimeException('Cannot create JSON string.'); + /** @psalm-suppress ImpureFunctionCall */ + throw new RuntimeException('Cannot create JSON string: '.json_last_error_msg()); } return $result; diff --git a/src/Psalm/Type/Atomic/TLiteralString.php b/src/Psalm/Type/Atomic/TLiteralString.php index 5c585923656..fc17b001248 100644 --- a/src/Psalm/Type/Atomic/TLiteralString.php +++ b/src/Psalm/Type/Atomic/TLiteralString.php @@ -2,8 +2,8 @@ namespace Psalm\Type\Atomic; use function preg_replace; -use function strlen; -use function substr; +use function mb_strlen; +use function mb_substr; /** * Denotes a string whose value is known. @@ -31,8 +31,8 @@ public function __toString(): string public function getId(bool $nested = false): string { $no_newline_value = preg_replace("/\n/m", '\n', $this->value); - if (strlen($this->value) > 80) { - return '"' . substr($no_newline_value, 0, 80) . '...' . '"'; + if (mb_strlen($this->value) > 80) { + return '"' . mb_substr($no_newline_value, 0, 80) . '...' . '"'; } return '"' . $no_newline_value . '"'; diff --git a/tests/TypeParseTest.php b/tests/TypeParseTest.php index 600c5e15651..590ca37d5ef 100644 --- a/tests/TypeParseTest.php +++ b/tests/TypeParseTest.php @@ -3,10 +3,11 @@ use function function_exists; use function print_r; +use function mb_substr; +use function stripos; use Psalm\Internal\RuntimeCaches; use Psalm\Type; -use function stripos; class TypeParseTest extends TestCase { @@ -860,6 +861,15 @@ public function testEnumWithoutSpaces(): void $this->assertSame($resolved_type->getId(), $docblock_type->getId()); } + public function testLongUtf8LiteralString(): void + { + $string = "АаБбВвГгДдЕеЁёЖжЗзИиЙйКкЛлМмНнОоПпРрСсТтУуФфХхЦцЧчШшЩщЪъЫыЬьЭэЮюЯя"; + $string .= $string; + $expected = mb_substr($string, 0, 80); + $this->assertSame("\"$expected...\"", Type:: parseString("'$string'")->getId()); + $this->assertSame("\"$expected...\"", Type:: parseString("\"$string\"")->getId()); + } + public function testSingleLiteralString(): void { $this->assertSame(