修复登陆日志入库错误,升级部分composer依赖项;

临时修复登陆通知邮件不能多发的问题;
This commit is contained in:
2025-07-13 19:37:43 +08:00
parent 037f274ec5
commit 45bc700e8f
93 changed files with 6405 additions and 6804 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ PHP Parser
This is a PHP parser written in PHP. Its purpose is to simplify static code analysis and
manipulation.
[**Documentation for version 5.x**][doc_master] (current; for running on PHP >= 7.4; for parsing PHP 7.0 to PHP 8.3, with limited support for parsing PHP 5.x).
[**Documentation for version 5.x**][doc_master] (current; for running on PHP >= 7.4; for parsing PHP 7.0 to PHP 8.4, with limited support for parsing PHP 5.x).
[Documentation for version 4.x][doc_4_x] (supported; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.3).
@@ -28,7 +28,7 @@ class ClassConst implements PhpParser\Builder {
* Creates a class constant builder
*
* @param string|Identifier $name Name
* @param Node\Expr|bool|null|int|float|string|array $value Value
* @param Node\Expr|bool|null|int|float|string|array|\UnitEnum $value Value
*/
public function __construct($name, $value) {
$this->constants = [new Const_($name, BuilderHelpers::normalizeValue($value))];
@@ -38,7 +38,7 @@ class ClassConst implements PhpParser\Builder {
* Add another constant to const group
*
* @param string|Identifier $name Name
* @param Node\Expr|bool|null|int|float|string|array $value Value
* @param Node\Expr|bool|null|int|float|string|array|\UnitEnum $value Value
*
* @return $this The builder instance (for fluid interface)
*/
@@ -13,7 +13,6 @@ use PhpParser\Node\Stmt;
class EnumCase implements PhpParser\Builder {
/** @var Identifier|string */
protected $name;
/** @var ?Node\Expr */
protected ?Node\Expr $value = null;
/** @var array<string, mixed> */
protected array $attributes = [];
+22
View File
@@ -122,6 +122,28 @@ class Param implements PhpParser\Builder {
return $this;
}
/**
* Gives the promoted property private(set) visibility.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePrivateSet() {
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE_SET);
return $this;
}
/**
* Gives the promoted property protected(set) visibility.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeProtectedSet() {
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED_SET);
return $this;
}
/**
* Adds an attribute group.
*
+63 -1
View File
@@ -23,6 +23,8 @@ class Property implements PhpParser\Builder {
protected ?Node $type = null;
/** @var list<Node\AttributeGroup> */
protected array $attributeGroups = [];
/** @var list<Node\PropertyHook> */
protected array $hooks = [];
/**
* Creates a property builder.
@@ -88,6 +90,50 @@ class Property implements PhpParser\Builder {
return $this;
}
/**
* Makes the property abstract. Requires at least one property hook to be specified as well.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeAbstract() {
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::ABSTRACT);
return $this;
}
/**
* Makes the property final.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeFinal() {
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::FINAL);
return $this;
}
/**
* Gives the property private(set) visibility.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePrivateSet() {
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE_SET);
return $this;
}
/**
* Gives the property protected(set) visibility.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeProtectedSet() {
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED_SET);
return $this;
}
/**
* Sets default value for the property.
*
@@ -142,12 +188,27 @@ class Property implements PhpParser\Builder {
return $this;
}
/**
* Adds a property hook.
*
* @return $this The builder instance (for fluid interface)
*/
public function addHook(Node\PropertyHook $hook) {
$this->hooks[] = $hook;
return $this;
}
/**
* Returns the built class node.
*
* @return Stmt\Property The built property node
*/
public function getNode(): PhpParser\Node {
if ($this->flags & Modifiers::ABSTRACT && !$this->hooks) {
throw new PhpParser\Error('Only hooked properties may be declared abstract');
}
return new Stmt\Property(
$this->flags !== 0 ? $this->flags : Modifiers::PUBLIC,
[
@@ -155,7 +216,8 @@ class Property implements PhpParser\Builder {
],
$this->attributes,
$this->type,
$this->attributeGroups
$this->attributeGroups,
$this->hooks
);
}
}
+1 -1
View File
@@ -210,7 +210,7 @@ class BuilderFactory {
/**
* Creates node a for a literal value.
*
* @param Expr|bool|null|int|float|string|array $value $value
* @param Expr|bool|null|int|float|string|array|\UnitEnum $value $value
*/
public function val($value): Expr {
return BuilderHelpers::normalizeValue($value);
+6 -1
View File
@@ -6,6 +6,7 @@ use PhpParser\Node\ComplexType;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\NullableType;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;
@@ -214,7 +215,7 @@ final class BuilderHelpers {
* Normalizes a value: Converts nulls, booleans, integers,
* floats, strings and arrays into their respective nodes
*
* @param Node\Expr|bool|null|int|float|string|array $value The value to normalize
* @param Node\Expr|bool|null|int|float|string|array|\UnitEnum $value The value to normalize
*
* @return Expr The normalized value
*/
@@ -268,6 +269,10 @@ final class BuilderHelpers {
return new Expr\Array_($items);
}
if ($value instanceof \UnitEnum) {
return new Expr\ClassConstFetch(new FullyQualified(\get_class($value)), new Identifier($value->name));
}
throw new \LogicException('Invalid value');
}
@@ -20,9 +20,9 @@ class TokenStream {
*
* @param Token[] $tokens Tokens in PhpToken::tokenize() format
*/
public function __construct(array $tokens) {
public function __construct(array $tokens, int $tabWidth) {
$this->tokens = $tokens;
$this->indentMap = $this->calcIndentMap();
$this->indentMap = $this->calcIndentMap($tabWidth);
}
/**
@@ -248,7 +248,7 @@ class TokenStream {
*
* @return int[] Token position to indentation map
*/
private function calcIndentMap(): array {
private function calcIndentMap(int $tabWidth): array {
$indentMap = [];
$indent = 0;
foreach ($this->tokens as $i => $token) {
@@ -258,11 +258,11 @@ class TokenStream {
$content = $token->text;
$newlinePos = \strrpos($content, "\n");
if (false !== $newlinePos) {
$indent = \strlen($content) - $newlinePos - 1;
$indent = $this->getIndent(\substr($content, $newlinePos + 1), $tabWidth);
} elseif ($i === 1 && $this->tokens[0]->id === \T_OPEN_TAG &&
$this->tokens[0]->text[\strlen($this->tokens[0]->text) - 1] === "\n") {
// Special case: Newline at the end of opening tag followed by whitespace.
$indent = \strlen($content);
$indent = $this->getIndent($content, $tabWidth);
}
}
}
@@ -272,4 +272,11 @@ class TokenStream {
return $indentMap;
}
private function getIndent(string $ws, int $tabWidth): int {
$spaces = \substr_count($ws, " ");
$tabs = \substr_count($ws, "\t");
assert(\strlen($ws) === $spaces + $tabs);
return $spaces + $tabs * $tabWidth;
}
}
+4 -4
View File
@@ -5,15 +5,13 @@ namespace PhpParser\Lexer;
use PhpParser\Error;
use PhpParser\ErrorHandler;
use PhpParser\Lexer;
use PhpParser\Lexer\TokenEmulator\AsymmetricVisibilityTokenEmulator;
use PhpParser\Lexer\TokenEmulator\AttributeEmulator;
use PhpParser\Lexer\TokenEmulator\EnumTokenEmulator;
use PhpParser\Lexer\TokenEmulator\CoaleseEqualTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ExplicitOctalEmulator;
use PhpParser\Lexer\TokenEmulator\FlexibleDocStringEmulator;
use PhpParser\Lexer\TokenEmulator\FnTokenEmulator;
use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator;
use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator;
use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator;
use PhpParser\Lexer\TokenEmulator\PropertyTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ReadonlyFunctionTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ReadonlyTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ReverseEmulator;
@@ -47,6 +45,8 @@ class Emulative extends Lexer {
new ReadonlyTokenEmulator(),
new ExplicitOctalEmulator(),
new ReadonlyFunctionTokenEmulator(),
new PropertyTokenEmulator(),
new AsymmetricVisibilityTokenEmulator(),
];
// Collect emulators that are relevant for the PHP version we're running
@@ -14,8 +14,12 @@ abstract class KeywordEmulator extends TokenEmulator {
/** @param Token[] $tokens */
protected function isKeywordContext(array $tokens, int $pos): bool {
$previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $pos);
return $previousNonSpaceToken === null || $previousNonSpaceToken->id !== \T_OBJECT_OPERATOR;
$prevToken = $this->getPreviousNonSpaceToken($tokens, $pos);
if ($prevToken === null) {
return false;
}
return $prevToken->id !== \T_OBJECT_OPERATOR
&& $prevToken->id !== \T_NULLSAFE_OBJECT_OPERATOR;
}
public function emulate(string $code, array $tokens): array {
+42 -26
View File
@@ -14,23 +14,47 @@ final class Modifiers {
public const ABSTRACT = 16;
public const FINAL = 32;
public const READONLY = 64;
public const PUBLIC_SET = 128;
public const PROTECTED_SET = 256;
public const PRIVATE_SET = 512;
public const VISIBILITY_MASK = 1 | 2 | 4;
public const VISIBILITY_MASK = self::PUBLIC | self::PROTECTED | self::PRIVATE;
public const VISIBILITY_SET_MASK = self::PUBLIC_SET | self::PROTECTED_SET | self::PRIVATE_SET;
private const TO_STRING_MAP = [
self::PUBLIC => 'public',
self::PROTECTED => 'protected',
self::PRIVATE => 'private',
self::STATIC => 'static',
self::ABSTRACT => 'abstract',
self::FINAL => 'final',
self::READONLY => 'readonly',
self::PUBLIC_SET => 'public(set)',
self::PROTECTED_SET => 'protected(set)',
self::PRIVATE_SET => 'private(set)',
];
public static function toString(int $modifier): string {
if (!isset(self::TO_STRING_MAP[$modifier])) {
throw new \InvalidArgumentException("Unknown modifier $modifier");
}
return self::TO_STRING_MAP[$modifier];
}
private static function isValidModifier(int $modifier): bool {
$isPow2 = ($modifier & ($modifier - 1)) == 0 && $modifier != 0;
return $isPow2 && $modifier <= self::PRIVATE_SET;
}
/**
* @internal
*/
public static function verifyClassModifier(int $a, int $b): void {
if ($a & Modifiers::ABSTRACT && $b & Modifiers::ABSTRACT) {
throw new Error('Multiple abstract modifiers are not allowed');
}
if ($a & Modifiers::FINAL && $b & Modifiers::FINAL) {
throw new Error('Multiple final modifiers are not allowed');
}
if ($a & Modifiers::READONLY && $b & Modifiers::READONLY) {
throw new Error('Multiple readonly modifiers are not allowed');
assert(self::isValidModifier($b));
if (($a & $b) != 0) {
throw new Error(
'Multiple ' . self::toString($b) . ' modifiers are not allowed');
}
if ($a & 48 && $b & 48) {
@@ -42,24 +66,16 @@ final class Modifiers {
* @internal
*/
public static function verifyModifier(int $a, int $b): void {
if ($a & Modifiers::VISIBILITY_MASK && $b & Modifiers::VISIBILITY_MASK) {
assert(self::isValidModifier($b));
if (($a & Modifiers::VISIBILITY_MASK && $b & Modifiers::VISIBILITY_MASK) ||
($a & Modifiers::VISIBILITY_SET_MASK && $b & Modifiers::VISIBILITY_SET_MASK)
) {
throw new Error('Multiple access type modifiers are not allowed');
}
if ($a & Modifiers::ABSTRACT && $b & Modifiers::ABSTRACT) {
throw new Error('Multiple abstract modifiers are not allowed');
}
if ($a & Modifiers::STATIC && $b & Modifiers::STATIC) {
throw new Error('Multiple static modifiers are not allowed');
}
if ($a & Modifiers::FINAL && $b & Modifiers::FINAL) {
throw new Error('Multiple final modifiers are not allowed');
}
if ($a & Modifiers::READONLY && $b & Modifiers::READONLY) {
throw new Error('Multiple readonly modifiers are not allowed');
if (($a & $b) != 0) {
throw new Error(
'Multiple ' . self::toString($b) . ' modifiers are not allowed');
}
if ($a & 48 && $b & 48) {
+1 -1
View File
@@ -185,7 +185,7 @@ class NameContext {
// Check for relevant type-specific use statements
foreach ($this->origAliases[$type] as $alias => $orig) {
if ($type === Stmt\Use_::TYPE_CONSTANT) {
// Constants are are complicated-sensitive
// Constants are complicated-sensitive
$normalizedOrig = $this->normalizeConstName($orig->toString());
if ($normalizedOrig === $this->normalizeConstName($name)) {
$possibleNames[] = new Name($alias);
+28 -2
View File
@@ -21,6 +21,8 @@ class Param extends NodeAbstract {
public int $flags;
/** @var AttributeGroup[] PHP attribute groups */
public array $attrGroups;
/** @var PropertyHook[] Property hooks for promoted properties */
public array $hooks;
/**
* Constructs a parameter node.
@@ -33,13 +35,15 @@ class Param extends NodeAbstract {
* @param array<string, mixed> $attributes Additional attributes
* @param int $flags Optional visibility flags
* @param list<AttributeGroup> $attrGroups PHP attribute groups
* @param PropertyHook[] $hooks Property hooks for promoted properties
*/
public function __construct(
Expr $var, ?Expr $default = null, ?Node $type = null,
bool $byRef = false, bool $variadic = false,
array $attributes = [],
int $flags = 0,
array $attrGroups = []
array $attrGroups = [],
array $hooks = []
) {
$this->attributes = $attributes;
$this->type = $type;
@@ -49,10 +53,11 @@ class Param extends NodeAbstract {
$this->default = $default;
$this->flags = $flags;
$this->attrGroups = $attrGroups;
$this->hooks = $hooks;
}
public function getSubNodeNames(): array {
return ['attrGroups', 'flags', 'type', 'byRef', 'variadic', 'var', 'default'];
return ['attrGroups', 'flags', 'type', 'byRef', 'variadic', 'var', 'default', 'hooks'];
}
public function getType(): string {
@@ -81,4 +86,25 @@ class Param extends NodeAbstract {
public function isReadonly(): bool {
return (bool) ($this->flags & Modifiers::READONLY);
}
/**
* Whether the promoted property has explicit public(set) visibility.
*/
public function isPublicSet(): bool {
return (bool) ($this->flags & Modifiers::PUBLIC_SET);
}
/**
* Whether the promoted property has explicit protected(set) visibility.
*/
public function isProtectedSet(): bool {
return (bool) ($this->flags & Modifiers::PROTECTED_SET);
}
/**
* Whether the promoted property has explicit private(set) visibility.
*/
public function isPrivateSet(): bool {
return (bool) ($this->flags & Modifiers::PRIVATE_SET);
}
}
@@ -0,0 +1,15 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar\MagicConst;
use PhpParser\Node\Scalar\MagicConst;
class Property extends MagicConst {
public function getName(): string {
return '__PROPERTY__';
}
public function getType(): string {
return 'Scalar_MagicConst_Property';
}
}
@@ -18,6 +18,8 @@ class Property extends Node\Stmt {
public ?Node $type;
/** @var Node\AttributeGroup[] PHP attribute groups */
public array $attrGroups;
/** @var Node\PropertyHook[] Property hooks */
public array $hooks;
/**
* Constructs a class property list node.
@@ -27,17 +29,19 @@ class Property extends Node\Stmt {
* @param array<string, mixed> $attributes Additional attributes
* @param null|Identifier|Name|ComplexType $type Type declaration
* @param Node\AttributeGroup[] $attrGroups PHP attribute groups
* @param Node\PropertyHook[] $hooks Property hooks
*/
public function __construct(int $flags, array $props, array $attributes = [], ?Node $type = null, array $attrGroups = []) {
public function __construct(int $flags, array $props, array $attributes = [], ?Node $type = null, array $attrGroups = [], array $hooks = []) {
$this->attributes = $attributes;
$this->flags = $flags;
$this->props = $props;
$this->type = $type;
$this->attrGroups = $attrGroups;
$this->hooks = $hooks;
}
public function getSubNodeNames(): array {
return ['attrGroups', 'flags', 'type', 'props'];
return ['attrGroups', 'flags', 'type', 'props', 'hooks'];
}
/**
@@ -76,6 +80,27 @@ class Property extends Node\Stmt {
return (bool) ($this->flags & Modifiers::READONLY);
}
/**
* Whether the property has explicit public(set) visibility.
*/
public function isPublicSet(): bool {
return (bool) ($this->flags & Modifiers::PUBLIC_SET);
}
/**
* Whether the property has explicit protected(set) visibility.
*/
public function isProtectedSet(): bool {
return (bool) ($this->flags & Modifiers::PROTECTED_SET);
}
/**
* Whether the property has explicit private(set) visibility.
*/
public function isPrivateSet(): bool {
return (bool) ($this->flags & Modifiers::PRIVATE_SET);
}
public function getType(): string {
return 'Stmt_Property';
}
+9
View File
@@ -185,6 +185,15 @@ class NodeDumper {
if ($flags & Modifiers::READONLY) {
$strs[] = 'READONLY';
}
if ($flags & Modifiers::PUBLIC_SET) {
$strs[] = 'PUBLIC_SET';
}
if ($flags & Modifiers::PROTECTED_SET) {
$strs[] = 'PROTECTED_SET';
}
if ($flags & Modifiers::PRIVATE_SET) {
$strs[] = 'PRIVATE_SET';
}
if ($strs) {
return implode(' | ', $strs) . ' (' . $flags . ')';
@@ -110,6 +110,12 @@ class NameResolver extends NodeVisitorAbstract {
$node->type = $this->resolveType($node->type);
}
$this->resolveAttrGroups($node);
} elseif ($node instanceof Node\PropertyHook) {
foreach ($node->params as $param) {
$param->type = $this->resolveType($param->type);
$this->resolveAttrGroups($param);
}
$this->resolveAttrGroups($node);
} elseif ($node instanceof Stmt\Const_) {
foreach ($node->consts as $const) {
$this->addNamespacedName($const);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+83 -58
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_;
@@ -1137,32 +1139,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 +1160,68 @@ abstract class ParserAbstract implements Parser {
}
}
/** @param PropertyHook[] $hooks */
protected function checkPropertyHookList(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 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 +1234,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) {
+1 -1
View File
@@ -43,7 +43,7 @@ class PhpVersion {
* if it is still under development.
*/
public static function getNewestSupported(): self {
return self::fromComponents(8, 3);
return self::fromComponents(8, 4);
}
/**
@@ -23,7 +23,8 @@ class Standard extends PrettyPrinterAbstract {
. ($node->byRef ? '&' : '')
. ($node->variadic ? '...' : '')
. $this->p($node->var)
. ($node->default ? ' = ' . $this->p($node->default) : '');
. ($node->default ? ' = ' . $this->p($node->default) : '')
. ($node->hooks ? ' {' . $this->pStmts($node->hooks) . $this->nl . '}' : '');
}
protected function pArg(Node\Arg $node): string {
@@ -125,6 +126,10 @@ class Standard extends PrettyPrinterAbstract {
return '__TRAIT__';
}
protected function pScalar_MagicConst_Property(MagicConst\Property $node): string {
return '__PROPERTY__';
}
// Scalars
private function indentString(string $str): string {
@@ -827,7 +832,8 @@ class Standard extends PrettyPrinterAbstract {
return $this->pAttrGroups($node->attrGroups)
. (0 === $node->flags ? 'var ' : $this->pModifiers($node->flags))
. ($node->type ? $this->p($node->type) . ' ' : '')
. $this->pCommaSeparated($node->props) . ';';
. $this->pCommaSeparated($node->props)
. ($node->hooks ? ' {' . $this->pStmts($node->hooks) . $this->nl . '}' : ';');
}
protected function pPropertyItem(Node\PropertyItem $node): string {
@@ -835,6 +841,15 @@ class Standard extends PrettyPrinterAbstract {
. (null !== $node->default ? ' = ' . $this->p($node->default) : '');
}
protected function pPropertyHook(Node\PropertyHook $node): string {
return $this->pAttrGroups($node->attrGroups)
. $this->pModifiers($node->flags)
. ($node->byRef ? '&' : '') . $node->name
. ($node->params ? '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')' : '')
. (\is_array($node->body) ? ' {' . $this->pStmts($node->body) . $this->nl . '}'
: ($node->body !== null ? ' => ' . $this->p($node->body) : '') . ';');
}
protected function pStmt_ClassMethod(Stmt\ClassMethod $node): string {
return $this->pAttrGroups($node->attrGroups)
. $this->pModifiers($node->flags)
@@ -14,6 +14,7 @@ use PhpParser\Node\Expr\Cast;
use PhpParser\Node\IntersectionType;
use PhpParser\Node\MatchArm;
use PhpParser\Node\Param;
use PhpParser\Node\PropertyHook;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;
use PhpParser\Node\UnionType;
@@ -105,6 +106,15 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
/** @var int Current indentation level. */
protected int $indentLevel;
/** @var string String for single level of indentation */
private string $indent;
/** @var int Width in spaces to indent by. */
private int $indentWidth;
/** @var bool Whether to use tab indentation. */
private bool $useTabs;
/** @var int Width in spaces of one tab. */
private int $tabWidth = 4;
/** @var string Newline style. Does not include current indentation. */
protected string $newline;
/** @var string Newline including current indentation. */
@@ -169,12 +179,14 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
* PHP version while specifying an older target (but the result will
* of course not be compatible with the older version in that case).
* * string $newline: The newline style to use. Should be "\n" (default) or "\r\n".
* * string $indent: The indentation to use. Should either be all spaces or a single
* tab. Defaults to four spaces (" ").
* * bool $shortArraySyntax: Whether to use [] instead of array() as the default array
* syntax, if the node does not specify a format. Defaults to whether
* the phpVersion support short array syntax.
*
* @param array{
* phpVersion?: PhpVersion, newline?: string, shortArraySyntax?: bool
* phpVersion?: PhpVersion, newline?: string, indent?: string, shortArraySyntax?: bool
* } $options Dictionary of formatting options
*/
public function __construct(array $options = []) {
@@ -189,6 +201,17 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
$options['shortArraySyntax'] ?? $this->phpVersion->supportsShortArraySyntax();
$this->docStringEndToken =
$this->phpVersion->supportsFlexibleHeredoc() ? null : '_DOC_STRING_END_' . mt_rand();
$this->indent = $indent = $options['indent'] ?? ' ';
if ($indent === "\t") {
$this->useTabs = true;
$this->indentWidth = $this->tabWidth;
} elseif ($indent === \str_repeat(' ', \strlen($indent))) {
$this->useTabs = false;
$this->indentWidth = \strlen($indent);
} else {
throw new \LogicException('Option "indent" must either be all spaces or a single tab');
}
}
/**
@@ -207,24 +230,29 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
*/
protected function setIndentLevel(int $level): void {
$this->indentLevel = $level;
$this->nl = $this->newline . \str_repeat(' ', $level);
if ($this->useTabs) {
$tabs = \intdiv($level, $this->tabWidth);
$spaces = $level % $this->tabWidth;
$this->nl = $this->newline . \str_repeat("\t", $tabs) . \str_repeat(' ', $spaces);
} else {
$this->nl = $this->newline . \str_repeat(' ', $level);
}
}
/**
* Increase indentation level.
*/
protected function indent(): void {
$this->indentLevel += 4;
$this->nl .= ' ';
$this->indentLevel += $this->indentWidth;
$this->nl .= $this->indent;
}
/**
* Decrease indentation level.
*/
protected function outdent(): void {
assert($this->indentLevel >= 4);
$this->indentLevel -= 4;
$this->nl = $this->newline . str_repeat(' ', $this->indentLevel);
assert($this->indentLevel >= $this->indentWidth);
$this->setIndentLevel($this->indentLevel - $this->indentWidth);
}
/**
@@ -536,7 +564,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
$this->initializeModifierChangeMap();
$this->resetState();
$this->origTokens = new TokenStream($origTokens);
$this->origTokens = new TokenStream($origTokens, $this->tabWidth);
$this->preprocessNodes($stmts);
@@ -708,7 +736,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
$result .= $extraLeft;
$origIndentLevel = $this->indentLevel;
$this->setIndentLevel($this->origTokens->getIndentationBefore($subStartPos) + $indentAdjustment);
$this->setIndentLevel(max($this->origTokens->getIndentationBefore($subStartPos) + $indentAdjustment, 0));
// If it's the same node that was previously in this position, it certainly doesn't
// need fixup. It's important to check this here, because our fixup checks are more
@@ -811,7 +839,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
\assert($itemStartPos >= 0 && $itemEndPos >= 0 && $itemStartPos >= $pos);
$origIndentLevel = $this->indentLevel;
$lastElemIndentLevel = $this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment;
$lastElemIndentLevel = max($this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment, 0);
$this->setIndentLevel($lastElemIndentLevel);
$comments = $arrItem->getComments();
@@ -1195,6 +1223,9 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
. ($modifiers & Modifiers::PUBLIC ? 'public ' : '')
. ($modifiers & Modifiers::PROTECTED ? 'protected ' : '')
. ($modifiers & Modifiers::PRIVATE ? 'private ' : '')
. ($modifiers & Modifiers::PUBLIC_SET ? 'public(set) ' : '')
. ($modifiers & Modifiers::PROTECTED_SET ? 'protected(set) ' : '')
. ($modifiers & Modifiers::PRIVATE_SET ? 'private(set) ' : '')
. ($modifiers & Modifiers::STATIC ? 'static ' : '')
. ($modifiers & Modifiers::READONLY ? 'readonly ' : '');
}
@@ -1517,6 +1548,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
Stmt\UseUse::class . '->uses' => ', ',
MatchArm::class . '->conds' => ', ',
AttributeGroup::class . '->attrs' => ', ',
PropertyHook::class . '->params' => ', ',
// statement lists
Expr\Closure::class . '->stmts' => "\n",
@@ -1554,10 +1586,15 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
Expr\Closure::class . '->attrGroups' => ' ',
Expr\ArrowFunction::class . '->attrGroups' => ' ',
Param::class . '->attrGroups' => ' ',
PropertyHook::class . '->attrGroups' => ' ',
Stmt\Switch_::class . '->cases' => "\n",
Stmt\TraitUse::class . '->adaptations' => "\n",
Stmt\TryCatch::class . '->stmts' => "\n",
Stmt\While_::class . '->stmts' => "\n",
PropertyHook::class . '->body' => "\n",
Stmt\Property::class . '->hooks' => "\n",
Param::class . '->hooks' => "\n",
// dummy for top-level context
'File->stmts' => "\n",
@@ -1641,6 +1678,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
Stmt\Property::class . '->flags' => ['pModifiers', \T_VARIABLE],
PrintableNewAnonClassNode::class . '->flags' => ['pModifiers', \T_CLASS],
Param::class . '->flags' => ['pModifiers', \T_VARIABLE],
PropertyHook::class . '->flags' => ['pModifiers', \T_STRING],
Expr\Closure::class . '->static' => ['pStatic', \T_FUNCTION],
Expr\ArrowFunction::class . '->static' => ['pStatic', \T_FN],
//Stmt\TraitUseAdaptation\Alias::class . '->newModifier' => 0, // TODO
@@ -17,6 +17,11 @@ if (!\function_exists('PhpParser\defineCompatibilityTokens')) {
'T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG',
'T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG',
'T_READONLY',
// PHP 8.4
'T_PROPERTY_C',
'T_PUBLIC_SET',
'T_PROTECTED_SET',
'T_PRIVATE_SET',
];
// PHP-Parser might be used together with another library that also emulates some or all