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: | |