1<?php declare(strict_types=1); 2 3namespace PhpParser\Lexer\TokenEmulator; 4 5use PhpParser\PhpVersion; 6use PhpParser\Token; 7 8final class NullsafeTokenEmulator extends TokenEmulator { 9 public function getPhpVersion(): PhpVersion { 10 return PhpVersion::fromComponents(8, 0); 11 } 12 13 public function isEmulationNeeded(string $code): bool { 14 return strpos($code, '?->') !== false; 15 } 16 17 public function emulate(string $code, array $tokens): array { 18 // We need to manually iterate and manage a count because we'll change 19 // the tokens array on the way 20 for ($i = 0, $c = count($tokens); $i < $c; ++$i) { 21 $token = $tokens[$i]; 22 if ($token->text === '?' && isset($tokens[$i + 1]) && $tokens[$i + 1]->id === \T_OBJECT_OPERATOR) { 23 array_splice($tokens, $i, 2, [ 24 new Token(\T_NULLSAFE_OBJECT_OPERATOR, '?->', $token->line, $token->pos), 25 ]); 26 $c--; 27 continue; 28 } 29 30 // Handle ?-> inside encapsed string. 31 if ($token->id === \T_ENCAPSED_AND_WHITESPACE && isset($tokens[$i - 1]) 32 && $tokens[$i - 1]->id === \T_VARIABLE 33 && preg_match('/^\?->([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)/', $token->text, $matches) 34 ) { 35 $replacement = [ 36 new Token(\T_NULLSAFE_OBJECT_OPERATOR, '?->', $token->line, $token->pos), 37 new Token(\T_STRING, $matches[1], $token->line, $token->pos + 3), 38 ]; 39 $matchLen = \strlen($matches[0]); 40 if ($matchLen !== \strlen($token->text)) { 41 $replacement[] = new Token( 42 \T_ENCAPSED_AND_WHITESPACE, 43 \substr($token->text, $matchLen), 44 $token->line, $token->pos + $matchLen 45 ); 46 } 47 array_splice($tokens, $i, 1, $replacement); 48 $c += \count($replacement) - 1; 49 continue; 50 } 51 } 52 53 return $tokens; 54 } 55 56 public function reverseEmulate(string $code, array $tokens): array { 57 // ?-> was not valid code previously, don't bother. 58 return $tokens; 59 } 60} 61