1<?php declare(strict_types=1); 2 3namespace PhpParser\Lexer\TokenEmulator; 4 5use PhpParser\PhpVersion; 6use PhpParser\Token; 7 8final class AsymmetricVisibilityTokenEmulator extends TokenEmulator { 9 public function getPhpVersion(): PhpVersion { 10 return PhpVersion::fromComponents(8, 4); 11 } 12 public function isEmulationNeeded(string $code): bool { 13 $code = strtolower($code); 14 return strpos($code, 'public(set)') !== false || 15 strpos($code, 'protected(set)') !== false || 16 strpos($code, 'private(set)') !== false; 17 } 18 19 public function emulate(string $code, array $tokens): array { 20 $map = [ 21 \T_PUBLIC => \T_PUBLIC_SET, 22 \T_PROTECTED => \T_PROTECTED_SET, 23 \T_PRIVATE => \T_PRIVATE_SET, 24 ]; 25 for ($i = 0, $c = count($tokens); $i < $c; ++$i) { 26 $token = $tokens[$i]; 27 if (isset($map[$token->id]) && $i + 3 < $c && $tokens[$i + 1]->text === '(' && 28 $tokens[$i + 2]->id === \T_STRING && \strtolower($tokens[$i + 2]->text) === 'set' && 29 $tokens[$i + 3]->text === ')' && 30 $this->isKeywordContext($tokens, $i) 31 ) { 32 array_splice($tokens, $i, 4, [ 33 new Token( 34 $map[$token->id], $token->text . '(' . $tokens[$i + 2]->text . ')', 35 $token->line, $token->pos), 36 ]); 37 $c -= 3; 38 } 39 } 40 41 return $tokens; 42 } 43 44 public function reverseEmulate(string $code, array $tokens): array { 45 $reverseMap = [ 46 \T_PUBLIC_SET => \T_PUBLIC, 47 \T_PROTECTED_SET => \T_PROTECTED, 48 \T_PRIVATE_SET => \T_PRIVATE, 49 ]; 50 for ($i = 0, $c = count($tokens); $i < $c; ++$i) { 51 $token = $tokens[$i]; 52 if (isset($reverseMap[$token->id]) && 53 \preg_match('/(public|protected|private)\((set)\)/i', $token->text, $matches) 54 ) { 55 [, $modifier, $set] = $matches; 56 $modifierLen = \strlen($modifier); 57 array_splice($tokens, $i, 1, [ 58 new Token($reverseMap[$token->id], $modifier, $token->line, $token->pos), 59 new Token(\ord('('), '(', $token->line, $token->pos + $modifierLen), 60 new Token(\T_STRING, $set, $token->line, $token->pos + $modifierLen + 1), 61 new Token(\ord(')'), ')', $token->line, $token->pos + $modifierLen + 4), 62 ]); 63 $i += 3; 64 $c += 3; 65 } 66 } 67 68 return $tokens; 69 } 70 71 /** @param Token[] $tokens */ 72 protected function isKeywordContext(array $tokens, int $pos): bool { 73 $prevToken = $this->getPreviousNonSpaceToken($tokens, $pos); 74 if ($prevToken === null) { 75 return false; 76 } 77 return $prevToken->id !== \T_OBJECT_OPERATOR 78 && $prevToken->id !== \T_NULLSAFE_OBJECT_OPERATOR; 79 } 80 81 /** @param Token[] $tokens */ 82 private function getPreviousNonSpaceToken(array $tokens, int $start): ?Token { 83 for ($i = $start - 1; $i >= 0; --$i) { 84 if ($tokens[$i]->id === T_WHITESPACE) { 85 continue; 86 } 87 88 return $tokens[$i]; 89 } 90 91 return null; 92 } 93} 94