Add emulation for enum keyword

This commit is contained in:
Nikita Popov 2021-03-20 17:49:44 +01:00
parent 8165cf69fa
commit a8223f228a
9 changed files with 140 additions and 69 deletions

View File

@ -110,4 +110,5 @@
%token T_NAME_FULLY_QUALIFIED
%token T_NAME_QUALIFIED
%token T_NAME_RELATIVE
%token T_ATTRIBUTE
%token T_ATTRIBUTE
%token T_ENUM

View File

@ -421,6 +421,8 @@ class Lexer
'T_MATCH',
'T_NULLSAFE_OBJECT_OPERATOR',
'T_ATTRIBUTE',
// PHP 8.1
'T_ENUM',
];
// PHP-Parser might be used together with another library that also emulates some or all
@ -511,6 +513,7 @@ class Lexer
$tokenMap[\T_MATCH] = Tokens::T_MATCH;
$tokenMap[\T_NULLSAFE_OBJECT_OPERATOR] = Tokens::T_NULLSAFE_OBJECT_OPERATOR;
$tokenMap[\T_ATTRIBUTE] = Tokens::T_ATTRIBUTE;
$tokenMap[\T_ENUM] = Tokens::T_ENUM;
return $tokenMap;
}

View File

@ -6,6 +6,7 @@ use PhpParser\Error;
use PhpParser\ErrorHandler;
use PhpParser\Lexer;
use PhpParser\Lexer\TokenEmulator\AttributeEmulator;
use PhpParser\Lexer\TokenEmulator\EnumTokenEmulator;
use PhpParser\Lexer\TokenEmulator\CoaleseEqualTokenEmulator;
use PhpParser\Lexer\TokenEmulator\FlexibleDocStringEmulator;
use PhpParser\Lexer\TokenEmulator\FnTokenEmulator;
@ -14,13 +15,13 @@ use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator;
use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator;
use PhpParser\Lexer\TokenEmulator\ReverseEmulator;
use PhpParser\Lexer\TokenEmulator\TokenEmulator;
use PhpParser\Parser\Tokens;
class Emulative extends Lexer
{
const PHP_7_3 = '7.3dev';
const PHP_7_4 = '7.4dev';
const PHP_8_0 = '8.0dev';
const PHP_8_1 = '8.1dev';
/** @var mixed[] Patches used to reverse changes introduced in the code */
private $patches = [];
@ -38,7 +39,7 @@ class Emulative extends Lexer
*/
public function __construct(array $options = [])
{
$this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_0;
$this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_1;
unset($options['phpVersion']);
parent::__construct($options);
@ -51,6 +52,7 @@ class Emulative extends Lexer
new NumericLiteralSeparatorEmulator(),
new NullsafeTokenEmulator(),
new AttributeEmulator(),
new EnumTokenEmulator(),
];
// Collect emulators that are relevant for the PHP version we're running

View File

@ -0,0 +1,31 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
final class EnumTokenEmulator extends KeywordEmulator
{
public function getPhpVersion(): string
{
return Emulative::PHP_8_1;
}
public function getKeywordString(): string
{
return 'enum';
}
public function getKeywordToken(): int
{
return \T_ENUM;
}
protected function isKeywordContext(array $tokens, int $pos): bool
{
return parent::isKeywordContext($tokens, $pos)
&& isset($tokens[$pos + 2])
&& $tokens[$pos + 1][0] === \T_WHITESPACE
&& $tokens[$pos + 2][0] === \T_STRING;
}
}

View File

@ -12,16 +12,18 @@ abstract class KeywordEmulator extends TokenEmulator
return strpos(strtolower($code), $this->getKeywordString()) !== false;
}
protected function isKeywordContext(array $tokens, int $pos): bool
{
$previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $pos);
return $previousNonSpaceToken === null || $previousNonSpaceToken[0] !== \T_OBJECT_OPERATOR;
}
public function emulate(string $code, array $tokens): array
{
$keywordString = $this->getKeywordString();
foreach ($tokens as $i => $token) {
if ($token[0] === T_STRING && strtolower($token[1]) === $keywordString) {
$previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $i);
if ($previousNonSpaceToken !== null && $previousNonSpaceToken[0] === \T_OBJECT_OPERATOR) {
continue;
}
if ($token[0] === T_STRING && strtolower($token[1]) === $keywordString
&& $this->isKeywordContext($tokens, $i)) {
$tokens[$i][0] = $this->getKeywordToken();
}
}

View File

@ -17,11 +17,11 @@ use PhpParser\Node\Stmt;
*/
class Php5 extends \PhpParser\ParserAbstract
{
protected $tokenToSymbolMapSize = 392;
protected $tokenToSymbolMapSize = 393;
protected $actionTableSize = 1069;
protected $gotoTableSize = 580;
protected $invalidSymbol = 165;
protected $invalidSymbol = 166;
protected $errorSymbol = 1;
protected $defaultAction = -32766;
protected $unexpectedTokenRule = 32767;
@ -194,36 +194,37 @@ class Php5 extends \PhpParser\ParserAbstract
"']'",
"'\"'",
"T_NULLSAFE_OBJECT_OPERATOR",
"T_ATTRIBUTE"
"T_ATTRIBUTE",
"T_ENUM"
);
protected $tokenToSymbol = array(
0, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 55, 162, 165, 159, 54, 37, 165,
157, 158, 52, 49, 8, 50, 51, 53, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 31, 154,
43, 16, 45, 30, 67, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 69, 165, 161, 36, 165, 160, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 155, 35, 156, 57, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 1, 2, 3, 4,
0, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 55, 162, 166, 159, 54, 37, 166,
157, 158, 52, 49, 8, 50, 51, 53, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 31, 154,
43, 16, 45, 30, 67, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 69, 166, 161, 36, 166, 160, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 155, 35, 156, 57, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 1, 2, 3, 4,
5, 6, 7, 9, 10, 11, 12, 13, 14, 15,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
27, 28, 29, 32, 33, 34, 38, 39, 40, 41,
@ -237,7 +238,7 @@ class Php5 extends \PhpParser\ParserAbstract
124, 125, 126, 127, 128, 129, 130, 131, 163, 132,
133, 134, 135, 136, 137, 138, 139, 140, 141, 142,
143, 144, 145, 146, 147, 148, 149, 150, 151, 152,
153, 164
153, 164, 165
);
protected $action = array(

View File

@ -17,11 +17,11 @@ use PhpParser\Node\Stmt;
*/
class Php7 extends \PhpParser\ParserAbstract
{
protected $tokenToSymbolMapSize = 392;
protected $tokenToSymbolMapSize = 393;
protected $actionTableSize = 1162;
protected $gotoTableSize = 588;
protected $invalidSymbol = 165;
protected $invalidSymbol = 166;
protected $errorSymbol = 1;
protected $defaultAction = -32766;
protected $unexpectedTokenRule = 32767;
@ -194,36 +194,37 @@ class Php7 extends \PhpParser\ParserAbstract
"')'",
"'`'",
"'\"'",
"'$'"
"'$'",
"T_ENUM"
);
protected $tokenToSymbol = array(
0, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 55, 163, 165, 164, 54, 37, 165,
160, 161, 52, 49, 8, 50, 51, 53, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 31, 156,
43, 16, 45, 30, 67, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 69, 165, 157, 36, 165, 162, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 158, 35, 159, 57, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 1, 2, 3, 4,
0, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 55, 163, 166, 164, 54, 37, 166,
160, 161, 52, 49, 8, 50, 51, 53, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 31, 156,
43, 16, 45, 30, 67, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 69, 166, 157, 36, 166, 162, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 158, 35, 159, 57, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
166, 166, 166, 166, 166, 166, 1, 2, 3, 4,
5, 6, 7, 9, 10, 11, 12, 13, 14, 15,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
27, 28, 29, 32, 33, 34, 38, 39, 40, 41,
@ -237,7 +238,7 @@ class Php7 extends \PhpParser\ParserAbstract
124, 125, 126, 127, 128, 129, 130, 131, 132, 133,
134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
144, 145, 146, 147, 148, 149, 150, 151, 152, 153,
154, 155
154, 155, 165
);
protected $action = array(

View File

@ -141,4 +141,5 @@ final class Tokens
const T_NAME_QUALIFIED = 389;
const T_NAME_RELATIVE = 390;
const T_ATTRIBUTE = 391;
const T_ENUM = 392;
}

View File

@ -313,6 +313,35 @@ class EmulativeTest extends LexerTest
[Tokens::T_END_HEREDOC, " LABEL"],
[ord(','), ','],
]],
// Enums use a contextual keyword
['enum Foo {}', [
[Tokens::T_ENUM, 'enum'],
[Tokens::T_STRING, 'Foo'],
[ord('{'), '{'],
[ord('}'), '}'],
]],
['class Enum {}', [
[Tokens::T_CLASS, 'class'],
[Tokens::T_STRING, 'Enum'],
[ord('{'), '{'],
[ord('}'), '}'],
]],
['class Enum extends X {}', [
[Tokens::T_CLASS, 'class'],
[Tokens::T_STRING, 'Enum'],
[Tokens::T_EXTENDS, 'extends'],
[Tokens::T_STRING, 'X'],
[ord('{'), '{'],
[ord('}'), '}'],
]],
['class Enum implements X {}', [
[Tokens::T_CLASS, 'class'],
[Tokens::T_STRING, 'Enum'],
[Tokens::T_IMPLEMENTS, 'implements'],
[Tokens::T_STRING, 'X'],
[ord('{'), '{'],
[ord('}'), '}'],
]],
];
}