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: /** @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: