Skip to content

Commit acaf3fe

Browse files
committed
Implement emulation of PHP 8 T_NAME_* tokens
Like comment emulation, this is unconditional, as it is required for core functionality.
1 parent a63b495 commit acaf3fe

File tree

2 files changed

+75
-5
lines changed

2 files changed

+75
-5
lines changed

lib/PhpParser/Lexer.php

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,25 @@ class Lexer
3434
* first three. For more info see getNextToken() docs.
3535
*/
3636
public function __construct(array $options = []) {
37-
// map from internal tokens to PhpParser tokens
38-
$this->tokenMap = $this->createTokenMap();
39-
40-
// Compatibility define for PHP < 7.4
37+
// Compatibility define for PHP < 7.4.
4138
if (!defined('T_BAD_CHARACTER')) {
4239
\define('T_BAD_CHARACTER', -1);
4340
}
4441

42+
// Compatibility defines for PHP < 8.0.
43+
if (!defined('T_NAME_QUALIFIED')) {
44+
\define('T_NAME_QUALIFIED', -2);
45+
}
46+
if (!defined('T_NAME_FULLY_QUALIFIED')) {
47+
\define('T_NAME_FULLY_QUALIFIED', -3);
48+
}
49+
if (!defined('T_NAME_RELATIVE')) {
50+
\define('T_NAME_RELATIVE', -4);
51+
}
52+
53+
// Create Map from internal tokens to PhpParser tokens.
54+
$this->tokenMap = $this->createTokenMap();
55+
4556
// map of tokens to drop while lexing (the map is only used for isset lookup,
4657
// that's why the value is simply set to 1; the value is never actually used.)
4758
$this->dropTokens = array_fill_keys(
@@ -138,7 +149,9 @@ protected function postprocessTokens(ErrorHandler $errorHandler) {
138149
// by checking if a trailing comment has a "*/" at the end.
139150
//
140151
// Additionally, we canonicalize to the PHP 8 comment format here, which does not include
141-
// the trailing whitespace anymore
152+
// the trailing whitespace anymore.
153+
//
154+
// We also canonicalize to the PHP 8 T_NAME_* tokens.
142155

143156
$filePos = 0;
144157
$line = 1;
@@ -170,6 +183,46 @@ protected function postprocessTokens(ErrorHandler $errorHandler) {
170183
}
171184
}
172185

186+
// Emulate PHP 8 T_NAME_* tokens, by combining sequences of T_NS_SEPARATOR and T_STRING
187+
// into a single token.
188+
// TODO: Also handle reserved keywords in namespaced names.
189+
if (\is_array($token)
190+
&& ($token[0] === \T_NS_SEPARATOR || $token[0] === \T_STRING || $token[0] === \T_NAMESPACE)) {
191+
$lastWasSeparator = $token[0] === \T_NS_SEPARATOR;
192+
$text = $token[1];
193+
for ($j = $i + 1; isset($this->tokens[$j]); $j++) {
194+
if ($lastWasSeparator) {
195+
if ($this->tokens[$j][0] !== \T_STRING) {
196+
break;
197+
}
198+
$lastWasSeparator = false;
199+
} else {
200+
if ($this->tokens[$j][0] !== \T_NS_SEPARATOR) {
201+
break;
202+
}
203+
$lastWasSeparator = true;
204+
}
205+
$text .= $this->tokens[$j][1];
206+
}
207+
if ($lastWasSeparator) {
208+
// Trailing separator is not part of the name.
209+
$j--;
210+
$text = substr($text, 0, -1);
211+
}
212+
if ($j > $i + 1) {
213+
if ($token[0] === \T_NS_SEPARATOR) {
214+
$type = \T_NAME_FULLY_QUALIFIED;
215+
} else if ($token[0] === \T_NAMESPACE) {
216+
$type = \T_NAME_RELATIVE;
217+
} else {
218+
$type = \T_NAME_QUALIFIED;
219+
}
220+
$token = [$type, $text, $line];
221+
array_splice($this->tokens, $i, $j - $i, [$token]);
222+
$numTokens -= $j - $i - 1;
223+
}
224+
}
225+
173226
$tokenValue = \is_string($token) ? $token : $token[1];
174227
$tokenLen = \strlen($tokenValue);
175228

@@ -409,6 +462,11 @@ protected function createTokenMap() : array {
409462
$tokenMap[\T_COMPILER_HALT_OFFSET] = Tokens::T_STRING;
410463
}
411464

465+
// Assign tokens for which we define compatibility constants, as token_name() does not know them.
466+
$tokenMap[\T_NAME_QUALIFIED] = Tokens::T_NAME_QUALIFIED;
467+
$tokenMap[\T_NAME_FULLY_QUALIFIED] = Tokens::T_NAME_FULLY_QUALIFIED;
468+
$tokenMap[\T_NAME_RELATIVE] = Tokens::T_NAME_RELATIVE;
469+
412470
return $tokenMap;
413471
}
414472
}

test/PhpParser/LexerTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,18 @@ public function provideTestLex() {
215215
[],
216216
[]
217217
],
218+
// tests PHP 8 T_NAME_* emulation
219+
[
220+
'<?php Foo\Bar \Foo\Bar namespace\Foo\Bar Foo\Bar\\',
221+
['usedAttributes' => []],
222+
[
223+
[Tokens::T_NAME_QUALIFIED, 'Foo\Bar', [], []],
224+
[Tokens::T_NAME_FULLY_QUALIFIED, '\Foo\Bar', [], []],
225+
[Tokens::T_NAME_RELATIVE, 'namespace\Foo\Bar', [], []],
226+
[Tokens::T_NAME_QUALIFIED, 'Foo\Bar', [], []],
227+
[Tokens::T_NS_SEPARATOR, '\\', [], []],
228+
]
229+
],
218230
];
219231
}
220232

0 commit comments

Comments
 (0)