| 1: | <?php declare(strict_types=1); |
| 2: | |
| 3: | namespace PhpParser\Lexer\TokenEmulator; |
| 4: | |
| 5: | use PhpParser\PhpVersion; |
| 6: | use PhpParser\Token; |
| 7: | |
| 8: | final 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: | |
| 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: | |
| 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: | |