This commit is contained in:
2025-05-12 14:25:25 +02:00
parent ab2db755ef
commit 9e378ca2b7
2719 changed files with 46505 additions and 60181 deletions

View File

@@ -7,6 +7,7 @@ namespace PhpParser;
* turn is based on work by Masato Bito.
*/
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\Cast\Double;
@@ -14,6 +15,7 @@ use PhpParser\Node\Identifier;
use PhpParser\Node\InterpolatedStringPart;
use PhpParser\Node\Name;
use PhpParser\Node\Param;
use PhpParser\Node\PropertyHook;
use PhpParser\Node\Scalar\InterpolatedString;
use PhpParser\Node\Scalar\Int_;
use PhpParser\Node\Scalar\String_;
@@ -30,6 +32,7 @@ use PhpParser\Node\Stmt\Nop;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\TryCatch;
use PhpParser\Node\UseItem;
use PhpParser\Node\VarLikeIdentifier;
use PhpParser\NodeVisitor\CommentAnnotatingVisitor;
abstract class ParserAbstract implements Parser {
@@ -318,7 +321,7 @@ abstract class ParserAbstract implements Parser {
try {
$callback = $this->reduceCallbacks[$rule];
if ($callback !== null) {
$callback($stackPos);
$callback($this, $stackPos);
} elseif ($ruleLength > 0) {
$this->semValue = $this->semStack[$stackPos - $ruleLength + 1];
}
@@ -409,8 +412,6 @@ abstract class ParserAbstract implements Parser {
$rule = $state - $this->numNonLeafStates;
}
}
throw new \RuntimeException('Reached end of parser loop');
}
protected function emitError(Error $error): void {
@@ -1137,32 +1138,12 @@ abstract class ParserAbstract implements Parser {
}
protected function checkClassConst(ClassConst $node, int $modifierPos): void {
if ($node->flags & Modifiers::STATIC) {
$this->emitError(new Error(
"Cannot use 'static' as constant modifier",
$this->getAttributesAt($modifierPos)));
}
if ($node->flags & Modifiers::ABSTRACT) {
$this->emitError(new Error(
"Cannot use 'abstract' as constant modifier",
$this->getAttributesAt($modifierPos)));
}
if ($node->flags & Modifiers::READONLY) {
$this->emitError(new Error(
"Cannot use 'readonly' as constant modifier",
$this->getAttributesAt($modifierPos)));
}
}
protected function checkProperty(Property $node, int $modifierPos): void {
if ($node->flags & Modifiers::ABSTRACT) {
$this->emitError(new Error('Properties cannot be declared abstract',
$this->getAttributesAt($modifierPos)));
}
if ($node->flags & Modifiers::FINAL) {
$this->emitError(new Error('Properties cannot be declared final',
$this->getAttributesAt($modifierPos)));
foreach ([Modifiers::STATIC, Modifiers::ABSTRACT, Modifiers::READONLY] as $modifier) {
if ($node->flags & $modifier) {
$this->emitError(new Error(
"Cannot use '" . Modifiers::toString($modifier) . "' as constant modifier",
$this->getAttributesAt($modifierPos)));
}
}
}
@@ -1178,6 +1159,89 @@ abstract class ParserAbstract implements Parser {
}
}
protected function checkPropertyHooksForMultiProperty(Property $property, int $hookPos): void {
if (count($property->props) > 1) {
$this->emitError(new Error(
'Cannot use hooks when declaring multiple properties', $this->getAttributesAt($hookPos)));
}
}
/** @param PropertyHook[] $hooks */
protected function checkEmptyPropertyHookList(array $hooks, int $hookPos): void {
if (empty($hooks)) {
$this->emitError(new Error(
'Property hook list cannot be empty', $this->getAttributesAt($hookPos)));
}
}
protected function checkPropertyHook(PropertyHook $hook, ?int $paramListPos): void {
$name = $hook->name->toLowerString();
if ($name !== 'get' && $name !== 'set') {
$this->emitError(new Error(
'Unknown hook "' . $hook->name . '", expected "get" or "set"',
$hook->name->getAttributes()));
}
if ($name === 'get' && $paramListPos !== null) {
$this->emitError(new Error(
'get hook must not have a parameter list', $this->getAttributesAt($paramListPos)));
}
}
protected function checkPropertyHookModifiers(int $a, int $b, int $modifierPos): void {
try {
Modifiers::verifyModifier($a, $b);
} catch (Error $error) {
$error->setAttributes($this->getAttributesAt($modifierPos));
$this->emitError($error);
}
if ($b != Modifiers::FINAL) {
$this->emitError(new Error(
'Cannot use the ' . Modifiers::toString($b) . ' modifier on a property hook',
$this->getAttributesAt($modifierPos)));
}
}
/**
* @param Property|Param $node
*/
protected function addPropertyNameToHooks(Node $node): void {
if ($node instanceof Property) {
$name = $node->props[0]->name->toString();
} else {
$name = $node->var->name;
}
foreach ($node->hooks as $hook) {
$hook->setAttribute('propertyName', $name);
}
}
/** @param array<Node\Arg|Node\VariadicPlaceholder> $args */
private function isSimpleExit(array $args): bool {
if (\count($args) === 0) {
return true;
}
if (\count($args) === 1) {
$arg = $args[0];
return $arg instanceof Arg && $arg->name === null &&
$arg->byRef === false && $arg->unpack === false;
}
return false;
}
/**
* @param array<Node\Arg|Node\VariadicPlaceholder> $args
* @param array<string, mixed> $attrs
*/
protected function createExitExpr(string $name, int $namePos, array $args, array $attrs): Expr {
if ($this->isSimpleExit($args)) {
// Create Exit node for backwards compatibility.
$attrs['kind'] = strtolower($name) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE;
return new Expr\Exit_(\count($args) === 1 ? $args[0]->value : null, $attrs);
}
return new Expr\FuncCall(new Name($name, $this->getAttributesAt($namePos)), $args, $attrs);
}
/**
* Creates the token map.
*
@@ -1190,42 +1254,23 @@ abstract class ParserAbstract implements Parser {
protected function createTokenMap(): array {
$tokenMap = [];
for ($i = 0; $i < 1000; ++$i) {
if ($i < 256) {
// Single-char tokens use an identity mapping.
$tokenMap[$i] = $i;
} elseif (\T_DOUBLE_COLON === $i) {
// T_DOUBLE_COLON is equivalent to T_PAAMAYIM_NEKUDOTAYIM
$tokenMap[$i] = static::T_PAAMAYIM_NEKUDOTAYIM;
} elseif (\T_OPEN_TAG_WITH_ECHO === $i) {
// T_OPEN_TAG_WITH_ECHO with dropped T_OPEN_TAG results in T_ECHO
$tokenMap[$i] = static::T_ECHO;
} elseif (\T_CLOSE_TAG === $i) {
// T_CLOSE_TAG is equivalent to ';'
$tokenMap[$i] = ord(';');
} elseif ('UNKNOWN' !== $name = token_name($i)) {
if (defined($name = static::class . '::' . $name)) {
// Other tokens can be mapped directly
$tokenMap[$i] = constant($name);
}
// Single-char tokens use an identity mapping.
for ($i = 0; $i < 256; ++$i) {
$tokenMap[$i] = $i;
}
foreach ($this->symbolToName as $name) {
if ($name[0] === 'T') {
$tokenMap[\constant($name)] = constant(static::class . '::' . $name);
}
}
// Assign tokens for which we define compatibility constants, as token_name() does not know them.
$tokenMap[\T_FN] = static::T_FN;
$tokenMap[\T_COALESCE_EQUAL] = static::T_COALESCE_EQUAL;
$tokenMap[\T_NAME_QUALIFIED] = static::T_NAME_QUALIFIED;
$tokenMap[\T_NAME_FULLY_QUALIFIED] = static::T_NAME_FULLY_QUALIFIED;
$tokenMap[\T_NAME_RELATIVE] = static::T_NAME_RELATIVE;
$tokenMap[\T_MATCH] = static::T_MATCH;
$tokenMap[\T_NULLSAFE_OBJECT_OPERATOR] = static::T_NULLSAFE_OBJECT_OPERATOR;
$tokenMap[\T_ATTRIBUTE] = static::T_ATTRIBUTE;
$tokenMap[\T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG] = static::T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG;
$tokenMap[\T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG] = static::T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG;
$tokenMap[\T_ENUM] = static::T_ENUM;
$tokenMap[\T_READONLY] = static::T_READONLY;
// T_OPEN_TAG_WITH_ECHO with dropped T_OPEN_TAG results in T_ECHO
$tokenMap[\T_OPEN_TAG_WITH_ECHO] = static::T_ECHO;
// T_CLOSE_TAG is equivalent to ';'
$tokenMap[\T_CLOSE_TAG] = ord(';');
// We have create a map from PHP token IDs to external symbol IDs.
// We have created a map from PHP token IDs to external symbol IDs.
// Now map them to the internal symbol ID.
$fullTokenMap = [];
foreach ($tokenMap as $phpToken => $extSymbol) {