updated-packages

This commit is contained in:
RafficMohammed
2023-01-08 00:13:22 +05:30
parent 3ff7df7487
commit da241bacb6
12659 changed files with 563377 additions and 510538 deletions

View File

@@ -0,0 +1,132 @@
<?php
declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Const_;
use PhpParser\Node\Identifier;
use PhpParser\Node\Stmt;
class ClassConst implements PhpParser\Builder
{
protected $flags = 0;
protected $attributes = [];
protected $constants = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates a class constant builder
*
* @param string|Identifier $name Name
* @param Node\Expr|bool|null|int|float|string|array $value Value
*/
public function __construct($name, $value) {
$this->constants = [new Const_($name, BuilderHelpers::normalizeValue($value))];
}
/**
* Add another constant to const group
*
* @param string|Identifier $name Name
* @param Node\Expr|bool|null|int|float|string|array $value Value
*
* @return $this The builder instance (for fluid interface)
*/
public function addConst($name, $value) {
$this->constants[] = new Const_($name, BuilderHelpers::normalizeValue($value));
return $this;
}
/**
* Makes the constant public.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePublic() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PUBLIC);
return $this;
}
/**
* Makes the constant protected.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeProtected() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED);
return $this;
}
/**
* Makes the constant private.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePrivate() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE);
return $this;
}
/**
* Makes the constant final.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeFinal() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
return $this;
}
/**
* Sets doc comment for the constant.
*
* @param PhpParser\Comment\Doc|string $docComment Doc comment to set
*
* @return $this The builder instance (for fluid interface)
*/
public function setDocComment($docComment) {
$this->attributes = [
'comments' => [BuilderHelpers::normalizeDocComment($docComment)]
];
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built class node.
*
* @return Stmt\ClassConst The built constant node
*/
public function getNode(): PhpParser\Node {
return new Stmt\ClassConst(
$this->constants,
$this->flags,
$this->attributes,
$this->attributeGroups
);
}
}

View File

@@ -4,6 +4,7 @@ namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
@@ -20,6 +21,9 @@ class Class_ extends Declaration
protected $properties = [];
protected $methods = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates a class builder.
*
@@ -63,7 +67,7 @@ class Class_ extends Declaration
* @return $this The builder instance (for fluid interface)
*/
public function makeAbstract() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT);
$this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT);
return $this;
}
@@ -74,7 +78,13 @@ class Class_ extends Declaration
* @return $this The builder instance (for fluid interface)
*/
public function makeFinal() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
$this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
return $this;
}
public function makeReadonly() {
$this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_READONLY);
return $this;
}
@@ -106,6 +116,19 @@ class Class_ extends Declaration
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built class node.
*
@@ -117,6 +140,7 @@ class Class_ extends Declaration
'extends' => $this->extends,
'implements' => $this->implements,
'stmts' => array_merge($this->uses, $this->constants, $this->properties, $this->methods),
'attrGroups' => $this->attributeGroups,
], $this->attributes);
}
}

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Stmt;
class EnumCase implements PhpParser\Builder
{
protected $name;
protected $value = null;
protected $attributes = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates an enum case builder.
*
* @param string|Identifier $name Name
*/
public function __construct($name) {
$this->name = $name;
}
/**
* Sets the value.
*
* @param Node\Expr|string|int $value
*
* @return $this
*/
public function setValue($value) {
$this->value = BuilderHelpers::normalizeValue($value);
return $this;
}
/**
* Sets doc comment for the constant.
*
* @param PhpParser\Comment\Doc|string $docComment Doc comment to set
*
* @return $this The builder instance (for fluid interface)
*/
public function setDocComment($docComment) {
$this->attributes = [
'comments' => [BuilderHelpers::normalizeDocComment($docComment)]
];
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built enum case node.
*
* @return Stmt\EnumCase The built constant node
*/
public function getNode(): PhpParser\Node {
return new Stmt\EnumCase(
$this->name,
$this->value,
$this->attributes,
$this->attributeGroups
);
}
}

View File

@@ -0,0 +1,117 @@
<?php declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
class Enum_ extends Declaration
{
protected $name;
protected $scalarType = null;
protected $implements = [];
protected $uses = [];
protected $enumCases = [];
protected $constants = [];
protected $methods = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates an enum builder.
*
* @param string $name Name of the enum
*/
public function __construct(string $name) {
$this->name = $name;
}
/**
* Sets the scalar type.
*
* @param string|Identifier $type
*
* @return $this
*/
public function setScalarType($scalarType) {
$this->scalarType = BuilderHelpers::normalizeType($scalarType);
return $this;
}
/**
* Implements one or more interfaces.
*
* @param Name|string ...$interfaces Names of interfaces to implement
*
* @return $this The builder instance (for fluid interface)
*/
public function implement(...$interfaces) {
foreach ($interfaces as $interface) {
$this->implements[] = BuilderHelpers::normalizeName($interface);
}
return $this;
}
/**
* Adds a statement.
*
* @param Stmt|PhpParser\Builder $stmt The statement to add
*
* @return $this The builder instance (for fluid interface)
*/
public function addStmt($stmt) {
$stmt = BuilderHelpers::normalizeNode($stmt);
$targets = [
Stmt\TraitUse::class => &$this->uses,
Stmt\EnumCase::class => &$this->enumCases,
Stmt\ClassConst::class => &$this->constants,
Stmt\ClassMethod::class => &$this->methods,
];
$class = \get_class($stmt);
if (!isset($targets[$class])) {
throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType()));
}
$targets[$class][] = $stmt;
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built class node.
*
* @return Stmt\Enum_ The built enum node
*/
public function getNode() : PhpParser\Node {
return new Stmt\Enum_($this->name, [
'scalarType' => $this->scalarType,
'implements' => $this->implements,
'stmts' => array_merge($this->uses, $this->enumCases, $this->constants, $this->methods),
'attrGroups' => $this->attributeGroups,
], $this->attributes);
}
}

View File

@@ -61,8 +61,7 @@ abstract class FunctionLike extends Declaration
/**
* Sets the return type for PHP 7.
*
* @param string|Node\Name|Node\NullableType $type One of array, callable, string, int, float,
* bool, iterable, or a class/interface name.
* @param string|Node\Name|Node\Identifier|Node\ComplexType $type
*
* @return $this The builder instance (for fluid interface)
*/

View File

@@ -12,6 +12,9 @@ class Function_ extends FunctionLike
protected $name;
protected $stmts = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates a function builder.
*
@@ -34,6 +37,19 @@ class Function_ extends FunctionLike
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built function node.
*
@@ -45,6 +61,7 @@ class Function_ extends FunctionLike
'params' => $this->params,
'returnType' => $this->returnType,
'stmts' => $this->stmts,
'attrGroups' => $this->attributeGroups,
], $this->attributes);
}
}

View File

@@ -4,6 +4,7 @@ namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
@@ -14,6 +15,9 @@ class Interface_ extends Declaration
protected $constants = [];
protected $methods = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates an interface builder.
*
@@ -61,6 +65,19 @@ class Interface_ extends Declaration
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built interface node.
*
@@ -70,6 +87,7 @@ class Interface_ extends Declaration
return new Stmt\Interface_($this->name, [
'extends' => $this->extends,
'stmts' => array_merge($this->constants, $this->methods),
'attrGroups' => $this->attributeGroups,
], $this->attributes);
}
}

View File

@@ -15,6 +15,9 @@ class Method extends FunctionLike
/** @var array|null */
protected $stmts = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates a method builder.
*
@@ -112,6 +115,19 @@ class Method extends FunctionLike
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built method node.
*
@@ -124,6 +140,7 @@ class Method extends FunctionLike
'params' => $this->params,
'returnType' => $this->returnType,
'stmts' => $this->stmts,
'attrGroups' => $this->attributeGroups,
], $this->attributes);
}
}

View File

@@ -37,7 +37,7 @@ class Namespace_ extends Declaration
/**
* Returns the built node.
*
* @return Node The built node
* @return Stmt\Namespace_ The built node
*/
public function getNode() : Node {
return new Stmt\Namespace_($this->name, $this->stmts, $this->attributes);

View File

@@ -12,13 +12,16 @@ class Param implements PhpParser\Builder
protected $default = null;
/** @var string|Node\Name|Node\NullableType|null */
/** @var Node\Identifier|Node\Name|Node\NullableType|null */
protected $type = null;
protected $byRef = false;
protected $variadic = false;
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates a parameter builder.
*
@@ -44,7 +47,7 @@ class Param implements PhpParser\Builder
/**
* Sets type for the parameter.
*
* @param string|Node\Name|Node\NullableType $type Parameter type
* @param string|Node\Name|Node\Identifier|Node\ComplexType $type Parameter type
*
* @return $this The builder instance (for fluid interface)
*/
@@ -60,7 +63,7 @@ class Param implements PhpParser\Builder
/**
* Sets type for the parameter.
*
* @param string|Node\Name|Node\NullableType $type Parameter type
* @param string|Node\Name|Node\Identifier|Node\ComplexType $type Parameter type
*
* @return $this The builder instance (for fluid interface)
*
@@ -92,6 +95,19 @@ class Param implements PhpParser\Builder
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built parameter node.
*
@@ -100,7 +116,7 @@ class Param implements PhpParser\Builder
public function getNode() : Node {
return new Node\Param(
new Node\Expr\Variable($this->name),
$this->default, $this->type, $this->byRef, $this->variadic
$this->default, $this->type, $this->byRef, $this->variadic, [], 0, $this->attributeGroups
);
}
}

View File

@@ -4,7 +4,11 @@ namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
use PhpParser\Node\ComplexType;
class Property implements PhpParser\Builder
{
@@ -14,6 +18,12 @@ class Property implements PhpParser\Builder
protected $default = null;
protected $attributes = [];
/** @var null|Identifier|Name|NullableType */
protected $type;
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates a property builder.
*
@@ -67,6 +77,17 @@ class Property implements PhpParser\Builder
return $this;
}
/**
* Makes the property readonly.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeReadonly() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_READONLY);
return $this;
}
/**
* Sets default value for the property.
*
@@ -95,6 +116,32 @@ class Property implements PhpParser\Builder
return $this;
}
/**
* Sets the property type for PHP 7.4+.
*
* @param string|Name|Identifier|ComplexType $type
*
* @return $this
*/
public function setType($type) {
$this->type = BuilderHelpers::normalizeType($type);
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built class node.
*
@@ -106,7 +153,9 @@ class Property implements PhpParser\Builder
[
new Stmt\PropertyProperty($this->name, $this->default)
],
$this->attributes
$this->attributes,
$this->type,
$this->attributeGroups
);
}
}

View File

@@ -4,6 +4,7 @@ namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Stmt;
class Trait_ extends Declaration
@@ -13,6 +14,9 @@ class Trait_ extends Declaration
protected $properties = [];
protected $methods = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates an interface builder.
*
@@ -45,6 +49,19 @@ class Trait_ extends Declaration
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built trait node.
*
@@ -53,7 +70,8 @@ class Trait_ extends Declaration
public function getNode() : PhpParser\Node {
return new Stmt\Trait_(
$this->name, [
'stmts' => array_merge($this->uses, $this->properties, $this->methods)
'stmts' => array_merge($this->uses, $this->properties, $this->methods),
'attrGroups' => $this->attributeGroups,
], $this->attributes
);
}

View File

@@ -39,7 +39,7 @@ class Use_ implements Builder
/**
* Returns the built node.
*
* @return Node The built node
* @return Stmt\Use_ The built node
*/
public function getNode() : Node {
return new Stmt\Use_([

View File

@@ -8,11 +8,25 @@ use PhpParser\Node\Expr\BinaryOp\Concat;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Use_;
class BuilderFactory
{
/**
* Creates an attribute node.
*
* @param string|Name $name Name of the attribute
* @param array $args Attribute named arguments
*
* @return Node\Attribute
*/
public function attribute($name, array $args = []) : Node\Attribute {
return new Node\Attribute(
BuilderHelpers::normalizeName($name),
$this->args($args)
);
}
/**
* Creates a namespace builder.
*
@@ -57,6 +71,17 @@ class BuilderFactory
return new Builder\Trait_($name);
}
/**
* Creates an enum builder.
*
* @param string $name Name of the enum
*
* @return Builder\Enum_ The created enum builder
*/
public function enum(string $name) : Builder\Enum_ {
return new Builder\Enum_($name);
}
/**
* Creates a trait use builder.
*
@@ -77,7 +102,7 @@ class BuilderFactory
* @return Builder\TraitUseAdaptation The create trait use adaptation builder
*/
public function traitUseAdaptation($trait, $method = null) : Builder\TraitUseAdaptation {
if (is_null($method)) {
if ($method === null) {
$method = $trait;
$trait = null;
}
@@ -162,6 +187,29 @@ class BuilderFactory
return new Builder\Use_($name, Use_::TYPE_CONSTANT);
}
/**
* Creates a class constant builder.
*
* @param string|Identifier $name Name
* @param Node\Expr|bool|null|int|float|string|array $value Value
*
* @return Builder\ClassConst The created use const builder
*/
public function classConst($name, $value) : Builder\ClassConst {
return new Builder\ClassConst($name, $value);
}
/**
* Creates an enum case builder.
*
* @param string|Identifier $name Name
*
* @return Builder\EnumCase The created use const builder
*/
public function enumCase($name) : Builder\EnumCase {
return new Builder\EnumCase($name);
}
/**
* Creates node a for a literal value.
*
@@ -199,12 +247,14 @@ class BuilderFactory
*/
public function args(array $args) : array {
$normalizedArgs = [];
foreach ($args as $arg) {
if ($arg instanceof Arg) {
$normalizedArgs[] = $arg;
} else {
$normalizedArgs[] = new Arg(BuilderHelpers::normalizeValue($arg));
foreach ($args as $key => $arg) {
if (!($arg instanceof Arg)) {
$arg = new Arg(BuilderHelpers::normalizeValue($arg));
}
if (\is_string($key)) {
$arg->name = BuilderHelpers::normalizeIdentifier($key);
}
$normalizedArgs[] = $arg;
}
return $normalizedArgs;
}
@@ -283,7 +333,7 @@ class BuilderFactory
public function constFetch($name) : Expr\ConstFetch {
return new Expr\ConstFetch(BuilderHelpers::normalizeName($name));
}
/**
* Creates a property fetch node.
*

View File

@@ -2,6 +2,7 @@
namespace PhpParser;
use PhpParser\Node\ComplexType;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
@@ -26,7 +27,9 @@ final class BuilderHelpers
public static function normalizeNode($node) : Node {
if ($node instanceof Builder) {
return $node->getNode();
} elseif ($node instanceof Node) {
}
if ($node instanceof Node) {
return $node;
}
@@ -101,7 +104,27 @@ final class BuilderHelpers
* @return Name The normalized name
*/
public static function normalizeName($name) : Name {
return self::normalizeNameCommon($name, false);
if ($name instanceof Name) {
return $name;
}
if (is_string($name)) {
if (!$name) {
throw new \LogicException('Name cannot be empty');
}
if ($name[0] === '\\') {
return new Name\FullyQualified(substr($name, 1));
}
if (0 === strpos($name, 'namespace\\')) {
return new Name\Relative(substr($name, strlen('namespace\\')));
}
return new Name($name);
}
throw new \LogicException('Name must be a string or an instance of Node\Name');
}
/**
@@ -112,44 +135,17 @@ final class BuilderHelpers
* @return Name|Expr The normalized name or expression
*/
public static function normalizeNameOrExpr($name) {
return self::normalizeNameCommon($name, true);
}
/**
* Normalizes a name: Converts string names to Name nodes, optionally allowing expressions.
*
* @param Expr|Name|string $name The name to normalize
* @param bool $allowExpr Whether to also allow expressions
*
* @return Name|Expr The normalized name, or expression (if allowed)
*/
private static function normalizeNameCommon($name, bool $allowExpr) {
if ($name instanceof Name) {
if ($name instanceof Expr) {
return $name;
} elseif (is_string($name)) {
if (!$name) {
throw new \LogicException('Name cannot be empty');
}
if ($name[0] === '\\') {
return new Name\FullyQualified(substr($name, 1));
} elseif (0 === strpos($name, 'namespace\\')) {
return new Name\Relative(substr($name, strlen('namespace\\')));
} else {
return new Name($name);
}
}
if ($allowExpr) {
if ($name instanceof Expr) {
return $name;
}
if (!is_string($name) && !($name instanceof Name)) {
throw new \LogicException(
'Name must be a string or an instance of Node\Name or Node\Expr'
);
} else {
throw new \LogicException('Name must be a string or an instance of Node\Name');
}
return self::normalizeName($name);
}
/**
@@ -158,16 +154,19 @@ final class BuilderHelpers
* In particular, builtin types become Identifiers, custom types become Names and nullables
* are wrapped in NullableType nodes.
*
* @param string|Name|Identifier|NullableType $type The type to normalize
* @param string|Name|Identifier|ComplexType $type The type to normalize
*
* @return Name|Identifier|NullableType The normalized type
* @return Name|Identifier|ComplexType The normalized type
*/
public static function normalizeType($type) {
if (!is_string($type)) {
if (!$type instanceof Name && !$type instanceof Identifier
&& !$type instanceof NullableType) {
if (
!$type instanceof Name && !$type instanceof Identifier &&
!$type instanceof ComplexType
) {
throw new \LogicException(
'Type must be a string, or an instance of Name, Identifier or NullableType');
'Type must be a string, or an instance of Name, Identifier or ComplexType'
);
}
return $type;
}
@@ -179,7 +178,20 @@ final class BuilderHelpers
}
$builtinTypes = [
'array', 'callable', 'string', 'int', 'float', 'bool', 'iterable', 'void', 'object'
'array',
'callable',
'bool',
'int',
'float',
'string',
'iterable',
'void',
'object',
'null',
'false',
'mixed',
'never',
'true',
];
$lowerType = strtolower($type);
@@ -189,11 +201,14 @@ final class BuilderHelpers
$type = self::normalizeName($type);
}
if ($nullable && (string) $type === 'void') {
throw new \LogicException('void type cannot be nullable');
$notNullableTypes = [
'void', 'mixed', 'never',
];
if ($nullable && in_array((string) $type, $notNullableTypes)) {
throw new \LogicException(sprintf('%s type cannot be nullable', $type));
}
return $nullable ? new Node\NullableType($type) : $type;
return $nullable ? new NullableType($type) : $type;
}
/**
@@ -207,21 +222,33 @@ final class BuilderHelpers
public static function normalizeValue($value) : Expr {
if ($value instanceof Node\Expr) {
return $value;
} elseif (is_null($value)) {
}
if (is_null($value)) {
return new Expr\ConstFetch(
new Name('null')
);
} elseif (is_bool($value)) {
}
if (is_bool($value)) {
return new Expr\ConstFetch(
new Name($value ? 'true' : 'false')
);
} elseif (is_int($value)) {
}
if (is_int($value)) {
return new Scalar\LNumber($value);
} elseif (is_float($value)) {
}
if (is_float($value)) {
return new Scalar\DNumber($value);
} elseif (is_string($value)) {
}
if (is_string($value)) {
return new Scalar\String_($value);
} elseif (is_array($value)) {
}
if (is_array($value)) {
$items = [];
$lastKey = -1;
foreach ($value as $itemKey => $itemValue) {
@@ -240,9 +267,9 @@ final class BuilderHelpers
}
return new Expr\Array_($items);
} else {
throw new \LogicException('Invalid value');
}
throw new \LogicException('Invalid value');
}
/**
@@ -255,11 +282,33 @@ final class BuilderHelpers
public static function normalizeDocComment($docComment) : Comment\Doc {
if ($docComment instanceof Comment\Doc) {
return $docComment;
} elseif (is_string($docComment)) {
return new Comment\Doc($docComment);
} else {
throw new \LogicException('Doc comment must be a string or an instance of PhpParser\Comment\Doc');
}
if (is_string($docComment)) {
return new Comment\Doc($docComment);
}
throw new \LogicException('Doc comment must be a string or an instance of PhpParser\Comment\Doc');
}
/**
* Normalizes a attribute: Converts attribute to the Attribute Group if needed.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return Node\AttributeGroup The Attribute Group
*/
public static function normalizeAttribute($attribute) : Node\AttributeGroup
{
if ($attribute instanceof Node\AttributeGroup) {
return $attribute;
}
if (!($attribute instanceof Node\Attribute)) {
throw new \LogicException('Attribute must be an instance of PhpParser\Node\Attribute or PhpParser\Node\AttributeGroup');
}
return new Node\AttributeGroup([$attribute]);
}
/**
@@ -274,4 +323,13 @@ final class BuilderHelpers
Stmt\Class_::verifyModifier($modifiers, $modifier);
return $modifiers | $modifier;
}
/**
* Adds a modifier and returns new modifier bitmask.
* @return int New modifiers
*/
public static function addClassModifier(int $existingModifiers, int $modifierToSet) : int {
Stmt\Class_::verifyClassModifier($existingModifiers, $modifierToSet);
return $existingModifiers | $modifierToSet;
}
}

View File

@@ -5,9 +5,12 @@ namespace PhpParser;
class Comment implements \JsonSerializable
{
protected $text;
protected $line;
protected $filePos;
protected $tokenPos;
protected $startLine;
protected $startFilePos;
protected $startTokenPos;
protected $endLine;
protected $endFilePos;
protected $endTokenPos;
/**
* Constructs a comment node.
@@ -18,12 +21,17 @@ class Comment implements \JsonSerializable
* @param int $startTokenPos Token offset the comment started on
*/
public function __construct(
string $text, int $startLine = -1, int $startFilePos = -1, int $startTokenPos = -1
string $text,
int $startLine = -1, int $startFilePos = -1, int $startTokenPos = -1,
int $endLine = -1, int $endFilePos = -1, int $endTokenPos = -1
) {
$this->text = $text;
$this->line = $startLine;
$this->filePos = $startFilePos;
$this->tokenPos = $startTokenPos;
$this->startLine = $startLine;
$this->startFilePos = $startFilePos;
$this->startTokenPos = $startTokenPos;
$this->endLine = $endLine;
$this->endFilePos = $endFilePos;
$this->endTokenPos = $endTokenPos;
}
/**
@@ -38,28 +46,88 @@ class Comment implements \JsonSerializable
/**
* Gets the line number the comment started on.
*
* @return int Line number
* @return int Line number (or -1 if not available)
*/
public function getLine() : int {
return $this->line;
public function getStartLine() : int {
return $this->startLine;
}
/**
* Gets the file offset the comment started on.
*
* @return int File offset
* @return int File offset (or -1 if not available)
*/
public function getFilePos() : int {
return $this->filePos;
public function getStartFilePos() : int {
return $this->startFilePos;
}
/**
* Gets the token offset the comment started on.
*
* @return int Token offset (or -1 if not available)
*/
public function getStartTokenPos() : int {
return $this->startTokenPos;
}
/**
* Gets the line number the comment ends on.
*
* @return int Line number (or -1 if not available)
*/
public function getEndLine() : int {
return $this->endLine;
}
/**
* Gets the file offset the comment ends on.
*
* @return int File offset (or -1 if not available)
*/
public function getEndFilePos() : int {
return $this->endFilePos;
}
/**
* Gets the token offset the comment ends on.
*
* @return int Token offset (or -1 if not available)
*/
public function getEndTokenPos() : int {
return $this->endTokenPos;
}
/**
* Gets the line number the comment started on.
*
* @deprecated Use getStartLine() instead
*
* @return int Line number
*/
public function getLine() : int {
return $this->startLine;
}
/**
* Gets the file offset the comment started on.
*
* @deprecated Use getStartFilePos() instead
*
* @return int File offset
*/
public function getFilePos() : int {
return $this->startFilePos;
}
/**
* Gets the token offset the comment started on.
*
* @deprecated Use getStartTokenPos() instead
*
* @return int Token offset
*/
public function getTokenPos() : int {
return $this->tokenPos;
return $this->startTokenPos;
}
/**
@@ -159,9 +227,13 @@ class Comment implements \JsonSerializable
return [
'nodeType' => $type,
'text' => $this->text,
'line' => $this->line,
'filePos' => $this->filePos,
'tokenPos' => $this->tokenPos,
// TODO: Rename these to include "start".
'line' => $this->startLine,
'filePos' => $this->startFilePos,
'tokenPos' => $this->startTokenPos,
'endLine' => $this->endLine,
'endFilePos' => $this->endFilePos,
'endTokenPos' => $this->endTokenPos,
];
}
}

View File

@@ -2,6 +2,7 @@
namespace PhpParser;
use function array_merge;
use PhpParser\Node\Expr;
use PhpParser\Node\Scalar;
@@ -150,6 +151,8 @@ class ConstExprEvaluator
foreach ($expr->items as $item) {
if (null !== $item->key) {
$array[$this->evaluate($item->key)] = $this->evaluate($item->value);
} elseif ($item->unpack) {
$array = array_merge($array, $this->evaluate($item->value));
} else {
$array[] = $this->evaluate($item->value);
}

View File

@@ -17,6 +17,8 @@ use PhpParser\Node\Expr;
*/
class PrintableNewAnonClassNode extends Expr
{
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/** @var Node\Arg[] Arguments */
public $args;
/** @var null|Node\Name Name of extended class */
@@ -27,9 +29,11 @@ class PrintableNewAnonClassNode extends Expr
public $stmts;
public function __construct(
array $args, Node\Name $extends = null, array $implements, array $stmts, array $attributes
array $attrGroups, array $args, Node\Name $extends = null, array $implements,
array $stmts, array $attributes
) {
parent::__construct($attributes);
$this->attrGroups = $attrGroups;
$this->args = $args;
$this->extends = $extends;
$this->implements = $implements;
@@ -39,9 +43,10 @@ class PrintableNewAnonClassNode extends Expr
public static function fromNewNode(Expr\New_ $newNode) {
$class = $newNode->class;
assert($class instanceof Node\Stmt\Class_);
assert($class->name === null);
// We don't assert that $class->name is null here, to allow consumers to assign unique names
// to anonymous classes for their own purposes. We simplify ignore the name here.
return new self(
$newNode->args, $class->extends, $class->implements,
$class->attrGroups, $newNode->args, $class->extends, $class->implements,
$class->stmts, $newNode->getAttributes()
);
}
@@ -51,6 +56,6 @@ class PrintableNewAnonClassNode extends Expr
}
public function getSubNodeNames() : array {
return ['args', 'extends', 'implements', 'stmts'];
return ['attrGroups', 'args', 'extends', 'implements', 'stmts'];
}
}

View File

@@ -33,7 +33,7 @@ class TokenStream
* @return bool
*/
public function haveParens(int $startPos, int $endPos) : bool {
return $this->haveTokenImmediativelyBefore($startPos, '(')
return $this->haveTokenImmediatelyBefore($startPos, '(')
&& $this->haveTokenImmediatelyAfter($endPos, ')');
}
@@ -46,7 +46,8 @@ class TokenStream
* @return bool
*/
public function haveBraces(int $startPos, int $endPos) : bool {
return $this->haveTokenImmediativelyBefore($startPos, '{')
return ($this->haveTokenImmediatelyBefore($startPos, '{')
|| $this->haveTokenImmediatelyBefore($startPos, T_CURLY_OPEN))
&& $this->haveTokenImmediatelyAfter($endPos, '}');
}
@@ -60,7 +61,7 @@ class TokenStream
*
* @return bool Whether the expected token was found
*/
public function haveTokenImmediativelyBefore(int $pos, $expectedTokenType) : bool {
public function haveTokenImmediatelyBefore(int $pos, $expectedTokenType) : bool {
$tokens = $this->tokens;
$pos--;
for (; $pos >= 0; $pos--) {
@@ -170,7 +171,7 @@ class TokenStream
return $pos;
}
public function findRight($pos, $findTokenType) {
public function findRight(int $pos, $findTokenType) {
$tokens = $this->tokens;
for ($count = \count($tokens); $pos < $count; $pos++) {
$type = $tokens[$pos][0];
@@ -181,6 +182,35 @@ class TokenStream
return -1;
}
/**
* Whether the given position range contains a certain token type.
*
* @param int $startPos Starting position (inclusive)
* @param int $endPos Ending position (exclusive)
* @param int|string $tokenType Token type to look for
* @return bool Whether the token occurs in the given range
*/
public function haveTokenInRange(int $startPos, int $endPos, $tokenType) {
$tokens = $this->tokens;
for ($pos = $startPos; $pos < $endPos; $pos++) {
if ($tokens[$pos][0] === $tokenType) {
return true;
}
}
return false;
}
public function haveBracesInRange(int $startPos, int $endPos) {
return $this->haveTokenInRange($startPos, $endPos, '{')
|| $this->haveTokenInRange($startPos, $endPos, T_CURLY_OPEN)
|| $this->haveTokenInRange($startPos, $endPos, '}');
}
public function haveTagInRange(int $startPos, int $endPos): bool {
return $this->haveTokenInRange($startPos, $endPos, \T_OPEN_TAG)
|| $this->haveTokenInRange($startPos, $endPos, \T_CLOSE_TAG);
}
/**
* Get indentation before token position.
*

View File

@@ -73,7 +73,9 @@ class JsonDecoder
}
return new $className(
$value['text'], $value['line'] ?? -1, $value['filePos'] ?? -1, $value['tokenPos'] ?? -1
$value['text'],
$value['line'] ?? -1, $value['filePos'] ?? -1, $value['tokenPos'] ?? -1,
$value['endLine'] ?? -1, $value['endFilePos'] ?? -1, $value['endTokenPos'] ?? -1
);
}

View File

@@ -15,8 +15,15 @@ class Lexer
protected $tokenMap;
protected $dropTokens;
protected $identifierTokens;
protected $usedAttributes;
private $attributeStartLineUsed;
private $attributeEndLineUsed;
private $attributeStartTokenPosUsed;
private $attributeEndTokenPosUsed;
private $attributeStartFilePosUsed;
private $attributeEndFilePosUsed;
private $attributeCommentsUsed;
/**
* Creates a Lexer.
@@ -28,21 +35,28 @@ class Lexer
* first three. For more info see getNextToken() docs.
*/
public function __construct(array $options = []) {
// map from internal tokens to PhpParser tokens
// Create Map from internal tokens to PhpParser tokens.
$this->defineCompatibilityTokens();
$this->tokenMap = $this->createTokenMap();
$this->identifierTokens = $this->createIdentifierTokenMap();
// map of tokens to drop while lexing (the map is only used for isset lookup,
// that's why the value is simply set to 1; the value is never actually used.)
$this->dropTokens = array_fill_keys(
[\T_WHITESPACE, \T_OPEN_TAG, \T_COMMENT, \T_DOC_COMMENT], 1
[\T_WHITESPACE, \T_OPEN_TAG, \T_COMMENT, \T_DOC_COMMENT, \T_BAD_CHARACTER], 1
);
// the usedAttributes member is a map of the used attribute names to a dummy
// value (here "true")
$options += [
'usedAttributes' => ['comments', 'startLine', 'endLine'],
];
$this->usedAttributes = array_fill_keys($options['usedAttributes'], true);
$defaultAttributes = ['comments', 'startLine', 'endLine'];
$usedAttributes = array_fill_keys($options['usedAttributes'] ?? $defaultAttributes, true);
// Create individual boolean properties to make these checks faster.
$this->attributeStartLineUsed = isset($usedAttributes['startLine']);
$this->attributeEndLineUsed = isset($usedAttributes['endLine']);
$this->attributeStartTokenPosUsed = isset($usedAttributes['startTokenPos']);
$this->attributeEndTokenPosUsed = isset($usedAttributes['endTokenPos']);
$this->attributeStartFilePosUsed = isset($usedAttributes['startFilePos']);
$this->attributeEndFilePosUsed = isset($usedAttributes['endFilePos']);
$this->attributeCommentsUsed = isset($usedAttributes['comments']);
}
/**
@@ -71,9 +85,8 @@ class Lexer
$scream = ini_set('xdebug.scream', '0');
error_clear_last();
$this->tokens = @token_get_all($code);
$this->handleErrors($errorHandler);
$this->postprocessTokens($errorHandler);
if (false !== $scream) {
ini_set('xdebug.scream', $scream);
@@ -81,13 +94,9 @@ class Lexer
}
private function handleInvalidCharacterRange($start, $end, $line, ErrorHandler $errorHandler) {
$tokens = [];
for ($i = $start; $i < $end; $i++) {
$chr = $this->code[$i];
if ($chr === 'b' || $chr === 'B') {
// HHVM does not treat b" tokens correctly, so ignore these
continue;
}
if ($chr === "\0") {
// PHP cuts error message after null byte, so need special case
$errorMsg = 'Unexpected null byte';
@@ -97,6 +106,7 @@ class Lexer
);
}
$tokens[] = [\T_BAD_CHARACTER, $chr, $line];
$errorHandler->handleError(new Error($errorMsg, [
'startLine' => $line,
'endLine' => $line,
@@ -104,6 +114,7 @@ class Lexer
'endFilePos' => $i,
]));
}
return $tokens;
}
/**
@@ -117,43 +128,116 @@ class Lexer
&& substr($token[1], -2) !== '*/';
}
/**
* Check whether an error *may* have occurred during tokenization.
*
* @return bool
*/
private function errorMayHaveOccurred() : bool {
if (defined('HHVM_VERSION')) {
// In HHVM token_get_all() does not throw warnings, so we need to conservatively
// assume that an error occurred
return true;
}
return null !== error_get_last();
}
protected function handleErrors(ErrorHandler $errorHandler) {
if (!$this->errorMayHaveOccurred()) {
return;
}
protected function postprocessTokens(ErrorHandler $errorHandler) {
// PHP's error handling for token_get_all() is rather bad, so if we want detailed
// error information we need to compute it ourselves. Invalid character errors are
// detected by finding "gaps" in the token array. Unterminated comments are detected
// by checking if a trailing comment has a "*/" at the end.
//
// Additionally, we perform a number of canonicalizations here:
// * Use the PHP 8.0 comment format, which does not include trailing whitespace anymore.
// * Use PHP 8.0 T_NAME_* tokens.
// * Use PHP 8.1 T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG and
// T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG tokens used to disambiguate intersection types.
$filePos = 0;
$line = 1;
foreach ($this->tokens as $token) {
$numTokens = \count($this->tokens);
for ($i = 0; $i < $numTokens; $i++) {
$token = $this->tokens[$i];
// Since PHP 7.4 invalid characters are represented by a T_BAD_CHARACTER token.
// In this case we only need to emit an error.
if ($token[0] === \T_BAD_CHARACTER) {
$this->handleInvalidCharacterRange($filePos, $filePos + 1, $line, $errorHandler);
}
if ($token[0] === \T_COMMENT && substr($token[1], 0, 2) !== '/*'
&& preg_match('/(\r\n|\n|\r)$/D', $token[1], $matches)) {
$trailingNewline = $matches[0];
$token[1] = substr($token[1], 0, -strlen($trailingNewline));
$this->tokens[$i] = $token;
if (isset($this->tokens[$i + 1]) && $this->tokens[$i + 1][0] === \T_WHITESPACE) {
// Move trailing newline into following T_WHITESPACE token, if it already exists.
$this->tokens[$i + 1][1] = $trailingNewline . $this->tokens[$i + 1][1];
$this->tokens[$i + 1][2]--;
} else {
// Otherwise, we need to create a new T_WHITESPACE token.
array_splice($this->tokens, $i + 1, 0, [
[\T_WHITESPACE, $trailingNewline, $line],
]);
$numTokens++;
}
}
// Emulate PHP 8 T_NAME_* tokens, by combining sequences of T_NS_SEPARATOR and T_STRING
// into a single token.
if (\is_array($token)
&& ($token[0] === \T_NS_SEPARATOR || isset($this->identifierTokens[$token[0]]))) {
$lastWasSeparator = $token[0] === \T_NS_SEPARATOR;
$text = $token[1];
for ($j = $i + 1; isset($this->tokens[$j]); $j++) {
if ($lastWasSeparator) {
if (!isset($this->identifierTokens[$this->tokens[$j][0]])) {
break;
}
$lastWasSeparator = false;
} else {
if ($this->tokens[$j][0] !== \T_NS_SEPARATOR) {
break;
}
$lastWasSeparator = true;
}
$text .= $this->tokens[$j][1];
}
if ($lastWasSeparator) {
// Trailing separator is not part of the name.
$j--;
$text = substr($text, 0, -1);
}
if ($j > $i + 1) {
if ($token[0] === \T_NS_SEPARATOR) {
$type = \T_NAME_FULLY_QUALIFIED;
} else if ($token[0] === \T_NAMESPACE) {
$type = \T_NAME_RELATIVE;
} else {
$type = \T_NAME_QUALIFIED;
}
$token = [$type, $text, $line];
array_splice($this->tokens, $i, $j - $i, [$token]);
$numTokens -= $j - $i - 1;
}
}
if ($token === '&') {
$next = $i + 1;
while (isset($this->tokens[$next]) && $this->tokens[$next][0] === \T_WHITESPACE) {
$next++;
}
$followedByVarOrVarArg = isset($this->tokens[$next]) &&
($this->tokens[$next][0] === \T_VARIABLE || $this->tokens[$next][0] === \T_ELLIPSIS);
$this->tokens[$i] = $token = [
$followedByVarOrVarArg
? \T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
: \T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG,
'&',
$line,
];
}
$tokenValue = \is_string($token) ? $token : $token[1];
$tokenLen = \strlen($tokenValue);
if (substr($this->code, $filePos, $tokenLen) !== $tokenValue) {
// Something is missing, must be an invalid character
$nextFilePos = strpos($this->code, $tokenValue, $filePos);
$this->handleInvalidCharacterRange(
$badCharTokens = $this->handleInvalidCharacterRange(
$filePos, $nextFilePos, $line, $errorHandler);
$filePos = (int) $nextFilePos;
array_splice($this->tokens, $i, 0, $badCharTokens);
$numTokens += \count($badCharTokens);
$i += \count($badCharTokens);
}
$filePos += $tokenLen;
@@ -176,8 +260,9 @@ class Lexer
$this->tokens[] = [$isDocComment ? \T_DOC_COMMENT : \T_COMMENT, $comment, $line];
} else {
// Invalid characters at the end of the input
$this->handleInvalidCharacterRange(
$badCharTokens = $this->handleInvalidCharacterRange(
$filePos, \strlen($this->code), $line, $errorHandler);
$this->tokens = array_merge($this->tokens, $badCharTokens);
}
return;
}
@@ -230,13 +315,13 @@ class Lexer
$token = "\0";
}
if (isset($this->usedAttributes['startLine'])) {
if ($this->attributeStartLineUsed) {
$startAttributes['startLine'] = $this->line;
}
if (isset($this->usedAttributes['startTokenPos'])) {
if ($this->attributeStartTokenPosUsed) {
$startAttributes['startTokenPos'] = $this->pos;
}
if (isset($this->usedAttributes['startFilePos'])) {
if ($this->attributeStartFilePosUsed) {
$startAttributes['startFilePos'] = $this->filePos;
}
@@ -254,7 +339,8 @@ class Lexer
$value = $token[1];
$id = $this->tokenMap[$token[0]];
if (\T_CLOSE_TAG === $token[0]) {
$this->prevCloseTagHasNewline = false !== strpos($token[1], "\n");
$this->prevCloseTagHasNewline = false !== strpos($token[1], "\n")
|| false !== strpos($token[1], "\r");
} elseif (\T_INLINE_HTML === $token[0]) {
$startAttributes['hasLeadingNewline'] = $this->prevCloseTagHasNewline;
}
@@ -262,27 +348,33 @@ class Lexer
$this->line += substr_count($value, "\n");
$this->filePos += \strlen($value);
} else {
$origLine = $this->line;
$origFilePos = $this->filePos;
$this->line += substr_count($token[1], "\n");
$this->filePos += \strlen($token[1]);
if (\T_COMMENT === $token[0] || \T_DOC_COMMENT === $token[0]) {
if (isset($this->usedAttributes['comments'])) {
if ($this->attributeCommentsUsed) {
$comment = \T_DOC_COMMENT === $token[0]
? new Comment\Doc($token[1], $this->line, $this->filePos, $this->pos)
: new Comment($token[1], $this->line, $this->filePos, $this->pos);
? new Comment\Doc($token[1],
$origLine, $origFilePos, $this->pos,
$this->line, $this->filePos - 1, $this->pos)
: new Comment($token[1],
$origLine, $origFilePos, $this->pos,
$this->line, $this->filePos - 1, $this->pos);
$startAttributes['comments'][] = $comment;
}
}
$this->line += substr_count($token[1], "\n");
$this->filePos += \strlen($token[1]);
continue;
}
if (isset($this->usedAttributes['endLine'])) {
if ($this->attributeEndLineUsed) {
$endAttributes['endLine'] = $this->line;
}
if (isset($this->usedAttributes['endTokenPos'])) {
if ($this->attributeEndTokenPosUsed) {
$endAttributes['endTokenPos'] = $this->pos;
}
if (isset($this->usedAttributes['endFilePos'])) {
if ($this->attributeEndFilePosUsed) {
$endAttributes['endFilePos'] = $this->filePos - 1;
}
@@ -329,6 +421,66 @@ class Lexer
return substr($textAfter, strlen($matches[0]));
}
private function defineCompatibilityTokens() {
static $compatTokensDefined = false;
if ($compatTokensDefined) {
return;
}
$compatTokens = [
// PHP 7.4
'T_BAD_CHARACTER',
'T_FN',
'T_COALESCE_EQUAL',
// PHP 8.0
'T_NAME_QUALIFIED',
'T_NAME_FULLY_QUALIFIED',
'T_NAME_RELATIVE',
'T_MATCH',
'T_NULLSAFE_OBJECT_OPERATOR',
'T_ATTRIBUTE',
// PHP 8.1
'T_ENUM',
'T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG',
'T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG',
'T_READONLY',
];
// PHP-Parser might be used together with another library that also emulates some or all
// of these tokens. Perform a sanity-check that all already defined tokens have been
// assigned a unique ID.
$usedTokenIds = [];
foreach ($compatTokens as $token) {
if (\defined($token)) {
$tokenId = \constant($token);
$clashingToken = $usedTokenIds[$tokenId] ?? null;
if ($clashingToken !== null) {
throw new \Error(sprintf(
'Token %s has same ID as token %s, ' .
'you may be using a library with broken token emulation',
$token, $clashingToken
));
}
$usedTokenIds[$tokenId] = $token;
}
}
// Now define any tokens that have not yet been emulated. Try to assign IDs from -1
// downwards, but skip any IDs that may already be in use.
$newTokenId = -1;
foreach ($compatTokens as $token) {
if (!\defined($token)) {
while (isset($usedTokenIds[$newTokenId])) {
$newTokenId--;
}
\define($token, $newTokenId);
$newTokenId--;
}
}
$compatTokensDefined = true;
}
/**
* Creates the token map.
*
@@ -373,6 +525,36 @@ class Lexer
$tokenMap[\T_COMPILER_HALT_OFFSET] = Tokens::T_STRING;
}
// Assign tokens for which we define compatibility constants, as token_name() does not know them.
$tokenMap[\T_FN] = Tokens::T_FN;
$tokenMap[\T_COALESCE_EQUAL] = Tokens::T_COALESCE_EQUAL;
$tokenMap[\T_NAME_QUALIFIED] = Tokens::T_NAME_QUALIFIED;
$tokenMap[\T_NAME_FULLY_QUALIFIED] = Tokens::T_NAME_FULLY_QUALIFIED;
$tokenMap[\T_NAME_RELATIVE] = Tokens::T_NAME_RELATIVE;
$tokenMap[\T_MATCH] = Tokens::T_MATCH;
$tokenMap[\T_NULLSAFE_OBJECT_OPERATOR] = Tokens::T_NULLSAFE_OBJECT_OPERATOR;
$tokenMap[\T_ATTRIBUTE] = Tokens::T_ATTRIBUTE;
$tokenMap[\T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG] = Tokens::T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG;
$tokenMap[\T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG] = Tokens::T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG;
$tokenMap[\T_ENUM] = Tokens::T_ENUM;
$tokenMap[\T_READONLY] = Tokens::T_READONLY;
return $tokenMap;
}
private function createIdentifierTokenMap(): array {
// Based on semi_reserved production.
return array_fill_keys([
\T_STRING,
\T_STATIC, \T_ABSTRACT, \T_FINAL, \T_PRIVATE, \T_PROTECTED, \T_PUBLIC, \T_READONLY,
\T_INCLUDE, \T_INCLUDE_ONCE, \T_EVAL, \T_REQUIRE, \T_REQUIRE_ONCE, \T_LOGICAL_OR, \T_LOGICAL_XOR, \T_LOGICAL_AND,
\T_INSTANCEOF, \T_NEW, \T_CLONE, \T_EXIT, \T_IF, \T_ELSEIF, \T_ELSE, \T_ENDIF, \T_ECHO, \T_DO, \T_WHILE,
\T_ENDWHILE, \T_FOR, \T_ENDFOR, \T_FOREACH, \T_ENDFOREACH, \T_DECLARE, \T_ENDDECLARE, \T_AS, \T_TRY, \T_CATCH,
\T_FINALLY, \T_THROW, \T_USE, \T_INSTEADOF, \T_GLOBAL, \T_VAR, \T_UNSET, \T_ISSET, \T_EMPTY, \T_CONTINUE, \T_GOTO,
\T_FUNCTION, \T_CONST, \T_RETURN, \T_PRINT, \T_YIELD, \T_LIST, \T_SWITCH, \T_ENDSWITCH, \T_CASE, \T_DEFAULT,
\T_BREAK, \T_ARRAY, \T_CALLABLE, \T_EXTENDS, \T_IMPLEMENTS, \T_NAMESPACE, \T_TRAIT, \T_INTERFACE, \T_CLASS,
\T_CLASS_C, \T_TRAIT_C, \T_FUNC_C, \T_METHOD_C, \T_LINE, \T_FILE, \T_DIR, \T_NS_C, \T_HALT_COMPILER, \T_FN,
\T_MATCH,
], true);
}
}

View File

@@ -4,27 +4,92 @@ namespace PhpParser\Lexer;
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\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\ReadonlyTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ReverseEmulator;
use PhpParser\Lexer\TokenEmulator\TokenEmulator;
class Emulative extends \PhpParser\Lexer
class Emulative extends Lexer
{
const PHP_7_3 = '7.3.0dev';
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 = [];
/** @var TokenEmulator[] */
private $emulators = [];
/** @var string */
private $targetPhpVersion;
/**
* @var array Patches used to reverse changes introduced in the code
* @param mixed[] $options Lexer options. In addition to the usual options,
* accepts a 'phpVersion' string that specifies the
* version to emulate. Defaults to newest supported.
*/
private $patches;
public function __construct(array $options = [])
{
$this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_1;
unset($options['phpVersion']);
parent::__construct($options);
$emulators = [
new FlexibleDocStringEmulator(),
new FnTokenEmulator(),
new MatchTokenEmulator(),
new CoaleseEqualTokenEmulator(),
new NumericLiteralSeparatorEmulator(),
new NullsafeTokenEmulator(),
new AttributeEmulator(),
new EnumTokenEmulator(),
new ReadonlyTokenEmulator(),
new ExplicitOctalEmulator(),
];
// Collect emulators that are relevant for the PHP version we're running
// and the PHP version we're targeting for emulation.
foreach ($emulators as $emulator) {
$emulatorPhpVersion = $emulator->getPhpVersion();
if ($this->isForwardEmulationNeeded($emulatorPhpVersion)) {
$this->emulators[] = $emulator;
} else if ($this->isReverseEmulationNeeded($emulatorPhpVersion)) {
$this->emulators[] = new ReverseEmulator($emulator);
}
}
}
public function startLexing(string $code, ErrorHandler $errorHandler = null) {
$this->patches = [];
$preparedCode = $this->prepareCode($code);
if (null === $preparedCode) {
$emulators = array_filter($this->emulators, function($emulator) use($code) {
return $emulator->isEmulationNeeded($code);
});
if (empty($emulators)) {
// Nothing to emulate, yay
parent::startLexing($code, $errorHandler);
return;
}
$this->patches = [];
foreach ($emulators as $emulator) {
$code = $emulator->preprocessCode($code, $this->patches);
}
$collector = new ErrorHandler\Collecting();
parent::startLexing($preparedCode, $collector);
parent::startLexing($code, $collector);
$this->sortPatches();
$this->fixupTokens();
$errors = $collector->getErrors();
@@ -34,78 +99,40 @@ class Emulative extends \PhpParser\Lexer
$errorHandler->handleError($error);
}
}
foreach ($emulators as $emulator) {
$this->tokens = $emulator->emulate($code, $this->tokens);
}
}
/**
* Prepares code for emulation. If nothing has to be emulated null is returned.
*
* @param string $code
* @return null|string
*/
private function prepareCode(string $code) {
if (version_compare(\PHP_VERSION, self::PHP_7_3, '>=')) {
return null;
}
if (strpos($code, '<<<') === false) {
// Definitely doesn't contain heredoc/nowdoc
return null;
}
$flexibleDocStringRegex = <<<'REGEX'
/<<<[ \t]*(['"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\1\r?\n
(?:.*\r?\n)*?
(?<indentation>\h*)\2(?![a-zA-Z_\x80-\xff])(?<separator>(?:;?[\r\n])?)/x
REGEX;
if (!preg_match_all($flexibleDocStringRegex, $code, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE)) {
// No heredoc/nowdoc found
return null;
}
// Keep track of how much we need to adjust string offsets due to the modifications we
// already made
$posDelta = 0;
foreach ($matches as $match) {
$indentation = $match['indentation'][0];
$indentationStart = $match['indentation'][1];
$separator = $match['separator'][0];
$separatorStart = $match['separator'][1];
if ($indentation === '' && $separator !== '') {
// Ordinary heredoc/nowdoc
continue;
}
if ($indentation !== '') {
// Remove indentation
$indentationLen = strlen($indentation);
$code = substr_replace($code, '', $indentationStart + $posDelta, $indentationLen);
$this->patches[] = [$indentationStart + $posDelta, 'add', $indentation];
$posDelta -= $indentationLen;
}
if ($separator === '') {
// Insert newline as separator
$code = substr_replace($code, "\n", $separatorStart + $posDelta, 0);
$this->patches[] = [$separatorStart + $posDelta, 'remove', "\n"];
$posDelta += 1;
}
}
if (empty($this->patches)) {
// We did not end up emulating anything
return null;
}
return $code;
private function isForwardEmulationNeeded(string $emulatorPhpVersion): bool {
return version_compare(\PHP_VERSION, $emulatorPhpVersion, '<')
&& version_compare($this->targetPhpVersion, $emulatorPhpVersion, '>=');
}
private function fixupTokens() {
assert(count($this->patches) > 0);
private function isReverseEmulationNeeded(string $emulatorPhpVersion): bool {
return version_compare(\PHP_VERSION, $emulatorPhpVersion, '>=')
&& version_compare($this->targetPhpVersion, $emulatorPhpVersion, '<');
}
private function sortPatches()
{
// Patches may be contributed by different emulators.
// Make sure they are sorted by increasing patch position.
usort($this->patches, function($p1, $p2) {
return $p1[0] <=> $p2[0];
});
}
private function fixupTokens()
{
if (\count($this->patches) === 0) {
return;
}
// Load first patch
$patchIdx = 0;
list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
// We use a manual loop over the tokens, because we modify the array on the fly
@@ -113,7 +140,20 @@ REGEX;
for ($i = 0, $c = \count($this->tokens); $i < $c; $i++) {
$token = $this->tokens[$i];
if (\is_string($token)) {
// We assume that patches don't apply to string tokens
if ($patchPos === $pos) {
// Only support replacement for string tokens.
assert($patchType === 'replace');
$this->tokens[$i] = $patchText;
// Fetch the next patch
$patchIdx++;
if ($patchIdx >= \count($this->patches)) {
// No more patches, we're done
return;
}
list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
}
$pos += \strlen($token);
continue;
}
@@ -141,6 +181,11 @@ REGEX;
$token[1], $patchText, $patchPos - $pos + $posDelta, 0
);
$posDelta += $patchTextLen;
} else if ($patchType === 'replace') {
// Replace inside the token string
$this->tokens[$i][1] = substr_replace(
$token[1], $patchText, $patchPos - $pos + $posDelta, $patchTextLen
);
} else {
assert(false);
}
@@ -187,7 +232,7 @@ REGEX;
if ($patchType === 'add') {
$posDelta += strlen($patchText);
$lineDelta += substr_count($patchText, "\n");
} else {
} else if ($patchType === 'remove') {
$posDelta -= strlen($patchText);
$lineDelta -= substr_count($patchText, "\n");
}
@@ -200,4 +245,4 @@ REGEX;
$error->setAttributes($attrs);
}
}
}
}

View File

@@ -0,0 +1,56 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
final class AttributeEmulator extends TokenEmulator
{
public function getPhpVersion(): string
{
return Emulative::PHP_8_0;
}
public function isEmulationNeeded(string $code) : bool
{
return strpos($code, '#[') !== false;
}
public function emulate(string $code, array $tokens): array
{
// We need to manually iterate and manage a count because we'll change
// the tokens array on the way.
$line = 1;
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
if ($tokens[$i] === '#' && isset($tokens[$i + 1]) && $tokens[$i + 1] === '[') {
array_splice($tokens, $i, 2, [
[\T_ATTRIBUTE, '#[', $line]
]);
$c--;
continue;
}
if (\is_array($tokens[$i])) {
$line += substr_count($tokens[$i][1], "\n");
}
}
return $tokens;
}
public function reverseEmulate(string $code, array $tokens): array
{
// TODO
return $tokens;
}
public function preprocessCode(string $code, array &$patches): string {
$pos = 0;
while (false !== $pos = strpos($code, '#[', $pos)) {
// Replace #[ with %[
$code[$pos] = '%';
$patches[] = [$pos, 'replace', '#'];
$pos += 2;
}
return $code;
}
}

View File

@@ -0,0 +1,47 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
final class CoaleseEqualTokenEmulator extends TokenEmulator
{
public function getPhpVersion(): string
{
return Emulative::PHP_7_4;
}
public function isEmulationNeeded(string $code): bool
{
return strpos($code, '??=') !== false;
}
public function emulate(string $code, array $tokens): array
{
// We need to manually iterate and manage a count because we'll change
// the tokens array on the way
$line = 1;
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
if (isset($tokens[$i + 1])) {
if ($tokens[$i][0] === T_COALESCE && $tokens[$i + 1] === '=') {
array_splice($tokens, $i, 2, [
[\T_COALESCE_EQUAL, '??=', $line]
]);
$c--;
continue;
}
}
if (\is_array($tokens[$i])) {
$line += substr_count($tokens[$i][1], "\n");
}
}
return $tokens;
}
public function reverseEmulate(string $code, array $tokens): array
{
// ??= was not valid code previously, don't bother.
return $tokens;
}
}

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

@@ -0,0 +1,44 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
class ExplicitOctalEmulator extends TokenEmulator {
public function getPhpVersion(): string {
return Emulative::PHP_8_1;
}
public function isEmulationNeeded(string $code): bool {
return strpos($code, '0o') !== false || strpos($code, '0O') !== false;
}
public function emulate(string $code, array $tokens): array {
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
if ($tokens[$i][0] == \T_LNUMBER && $tokens[$i][1] === '0' &&
isset($tokens[$i + 1]) && $tokens[$i + 1][0] == \T_STRING &&
preg_match('/[oO][0-7]+(?:_[0-7]+)*/', $tokens[$i + 1][1])
) {
$tokenKind = $this->resolveIntegerOrFloatToken($tokens[$i + 1][1]);
array_splice($tokens, $i, 2, [
[$tokenKind, '0' . $tokens[$i + 1][1], $tokens[$i][2]],
]);
$c--;
}
}
return $tokens;
}
private function resolveIntegerOrFloatToken(string $str): int
{
$str = substr($str, 1);
$str = str_replace('_', '', $str);
$num = octdec($str);
return is_float($num) ? \T_DNUMBER : \T_LNUMBER;
}
public function reverseEmulate(string $code, array $tokens): array {
// Explicit octals were not legal code previously, don't bother.
return $tokens;
}
}

View File

@@ -0,0 +1,76 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
final class FlexibleDocStringEmulator extends TokenEmulator
{
const FLEXIBLE_DOC_STRING_REGEX = <<<'REGEX'
/<<<[ \t]*(['"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\1\r?\n
(?:.*\r?\n)*?
(?<indentation>\h*)\2(?![a-zA-Z0-9_\x80-\xff])(?<separator>(?:;?[\r\n])?)/x
REGEX;
public function getPhpVersion(): string
{
return Emulative::PHP_7_3;
}
public function isEmulationNeeded(string $code) : bool
{
return strpos($code, '<<<') !== false;
}
public function emulate(string $code, array $tokens): array
{
// Handled by preprocessing + fixup.
return $tokens;
}
public function reverseEmulate(string $code, array $tokens): array
{
// Not supported.
return $tokens;
}
public function preprocessCode(string $code, array &$patches): string {
if (!preg_match_all(self::FLEXIBLE_DOC_STRING_REGEX, $code, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE)) {
// No heredoc/nowdoc found
return $code;
}
// Keep track of how much we need to adjust string offsets due to the modifications we
// already made
$posDelta = 0;
foreach ($matches as $match) {
$indentation = $match['indentation'][0];
$indentationStart = $match['indentation'][1];
$separator = $match['separator'][0];
$separatorStart = $match['separator'][1];
if ($indentation === '' && $separator !== '') {
// Ordinary heredoc/nowdoc
continue;
}
if ($indentation !== '') {
// Remove indentation
$indentationLen = strlen($indentation);
$code = substr_replace($code, '', $indentationStart + $posDelta, $indentationLen);
$patches[] = [$indentationStart + $posDelta, 'add', $indentation];
$posDelta -= $indentationLen;
}
if ($separator === '') {
// Insert newline as separator
$code = substr_replace($code, "\n", $separatorStart + $posDelta, 0);
$patches[] = [$separatorStart + $posDelta, 'remove', "\n"];
$posDelta += 1;
}
}
return $code;
}
}

View File

@@ -0,0 +1,23 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
final class FnTokenEmulator extends KeywordEmulator
{
public function getPhpVersion(): string
{
return Emulative::PHP_7_4;
}
public function getKeywordString(): string
{
return 'fn';
}
public function getKeywordToken(): int
{
return \T_FN;
}
}

View File

@@ -0,0 +1,62 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
abstract class KeywordEmulator extends TokenEmulator
{
abstract function getKeywordString(): string;
abstract function getKeywordToken(): int;
public function isEmulationNeeded(string $code): bool
{
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
&& $this->isKeywordContext($tokens, $i)) {
$tokens[$i][0] = $this->getKeywordToken();
}
}
return $tokens;
}
/**
* @param mixed[] $tokens
* @return array|string|null
*/
private function getPreviousNonSpaceToken(array $tokens, int $start)
{
for ($i = $start - 1; $i >= 0; --$i) {
if ($tokens[$i][0] === T_WHITESPACE) {
continue;
}
return $tokens[$i];
}
return null;
}
public function reverseEmulate(string $code, array $tokens): array
{
$keywordToken = $this->getKeywordToken();
foreach ($tokens as $i => $token) {
if ($token[0] === $keywordToken) {
$tokens[$i][0] = \T_STRING;
}
}
return $tokens;
}
}

View File

@@ -0,0 +1,23 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
final class MatchTokenEmulator extends KeywordEmulator
{
public function getPhpVersion(): string
{
return Emulative::PHP_8_0;
}
public function getKeywordString(): string
{
return 'match';
}
public function getKeywordToken(): int
{
return \T_MATCH;
}
}

View File

@@ -0,0 +1,67 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
final class NullsafeTokenEmulator extends TokenEmulator
{
public function getPhpVersion(): string
{
return Emulative::PHP_8_0;
}
public function isEmulationNeeded(string $code): bool
{
return strpos($code, '?->') !== false;
}
public function emulate(string $code, array $tokens): array
{
// We need to manually iterate and manage a count because we'll change
// the tokens array on the way
$line = 1;
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
if ($tokens[$i] === '?' && isset($tokens[$i + 1]) && $tokens[$i + 1][0] === \T_OBJECT_OPERATOR) {
array_splice($tokens, $i, 2, [
[\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line]
]);
$c--;
continue;
}
// Handle ?-> inside encapsed string.
if ($tokens[$i][0] === \T_ENCAPSED_AND_WHITESPACE && isset($tokens[$i - 1])
&& $tokens[$i - 1][0] === \T_VARIABLE
&& preg_match('/^\?->([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)/', $tokens[$i][1], $matches)
) {
$replacement = [
[\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line],
[\T_STRING, $matches[1], $line],
];
if (\strlen($matches[0]) !== \strlen($tokens[$i][1])) {
$replacement[] = [
\T_ENCAPSED_AND_WHITESPACE,
\substr($tokens[$i][1], \strlen($matches[0])),
$line
];
}
array_splice($tokens, $i, 1, $replacement);
$c += \count($replacement) - 1;
continue;
}
if (\is_array($tokens[$i])) {
$line += substr_count($tokens[$i][1], "\n");
}
}
return $tokens;
}
public function reverseEmulate(string $code, array $tokens): array
{
// ?-> was not valid code previously, don't bother.
return $tokens;
}
}

View File

@@ -0,0 +1,105 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
final class NumericLiteralSeparatorEmulator extends TokenEmulator
{
const BIN = '(?:0b[01]+(?:_[01]+)*)';
const HEX = '(?:0x[0-9a-f]+(?:_[0-9a-f]+)*)';
const DEC = '(?:[0-9]+(?:_[0-9]+)*)';
const SIMPLE_FLOAT = '(?:' . self::DEC . '\.' . self::DEC . '?|\.' . self::DEC . ')';
const EXP = '(?:e[+-]?' . self::DEC . ')';
const FLOAT = '(?:' . self::SIMPLE_FLOAT . self::EXP . '?|' . self::DEC . self::EXP . ')';
const NUMBER = '~' . self::FLOAT . '|' . self::BIN . '|' . self::HEX . '|' . self::DEC . '~iA';
public function getPhpVersion(): string
{
return Emulative::PHP_7_4;
}
public function isEmulationNeeded(string $code) : bool
{
return preg_match('~[0-9]_[0-9]~', $code)
|| preg_match('~0x[0-9a-f]+_[0-9a-f]~i', $code);
}
public function emulate(string $code, array $tokens): array
{
// We need to manually iterate and manage a count because we'll change
// the tokens array on the way
$codeOffset = 0;
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
$token = $tokens[$i];
$tokenLen = \strlen(\is_array($token) ? $token[1] : $token);
if ($token[0] !== T_LNUMBER && $token[0] !== T_DNUMBER) {
$codeOffset += $tokenLen;
continue;
}
$res = preg_match(self::NUMBER, $code, $matches, 0, $codeOffset);
assert($res, "No number at number token position");
$match = $matches[0];
$matchLen = \strlen($match);
if ($matchLen === $tokenLen) {
// Original token already holds the full number.
$codeOffset += $tokenLen;
continue;
}
$tokenKind = $this->resolveIntegerOrFloatToken($match);
$newTokens = [[$tokenKind, $match, $token[2]]];
$numTokens = 1;
$len = $tokenLen;
while ($matchLen > $len) {
$nextToken = $tokens[$i + $numTokens];
$nextTokenText = \is_array($nextToken) ? $nextToken[1] : $nextToken;
$nextTokenLen = \strlen($nextTokenText);
$numTokens++;
if ($matchLen < $len + $nextTokenLen) {
// Split trailing characters into a partial token.
assert(is_array($nextToken), "Partial token should be an array token");
$partialText = substr($nextTokenText, $matchLen - $len);
$newTokens[] = [$nextToken[0], $partialText, $nextToken[2]];
break;
}
$len += $nextTokenLen;
}
array_splice($tokens, $i, $numTokens, $newTokens);
$c -= $numTokens - \count($newTokens);
$codeOffset += $matchLen;
}
return $tokens;
}
private function resolveIntegerOrFloatToken(string $str): int
{
$str = str_replace('_', '', $str);
if (stripos($str, '0b') === 0) {
$num = bindec($str);
} elseif (stripos($str, '0x') === 0) {
$num = hexdec($str);
} elseif (stripos($str, '0') === 0 && ctype_digit($str)) {
$num = octdec($str);
} else {
$num = +$str;
}
return is_float($num) ? T_DNUMBER : T_LNUMBER;
}
public function reverseEmulate(string $code, array $tokens): array
{
// Numeric separators were not legal code previously, don't bother.
return $tokens;
}
}

View File

@@ -0,0 +1,36 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
final class ReadonlyTokenEmulator extends KeywordEmulator
{
public function getPhpVersion(): string
{
return Emulative::PHP_8_1;
}
public function getKeywordString(): string
{
return 'readonly';
}
public function getKeywordToken(): int
{
return \T_READONLY;
}
protected function isKeywordContext(array $tokens, int $pos): bool
{
if (!parent::isKeywordContext($tokens, $pos)) {
return false;
}
// Support "function readonly("
return !(isset($tokens[$pos + 1]) &&
($tokens[$pos + 1][0] === '(' ||
($tokens[$pos + 1][0] === \T_WHITESPACE &&
isset($tokens[$pos + 2]) &&
$tokens[$pos + 2][0] === '(')));
}
}

View File

@@ -0,0 +1,36 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
/**
* Reverses emulation direction of the inner emulator.
*/
final class ReverseEmulator extends TokenEmulator
{
/** @var TokenEmulator Inner emulator */
private $emulator;
public function __construct(TokenEmulator $emulator) {
$this->emulator = $emulator;
}
public function getPhpVersion(): string {
return $this->emulator->getPhpVersion();
}
public function isEmulationNeeded(string $code): bool {
return $this->emulator->isEmulationNeeded($code);
}
public function emulate(string $code, array $tokens): array {
return $this->emulator->reverseEmulate($code, $tokens);
}
public function reverseEmulate(string $code, array $tokens): array {
return $this->emulator->emulate($code, $tokens);
}
public function preprocessCode(string $code, array &$patches): string {
return $code;
}
}

View File

@@ -0,0 +1,25 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
/** @internal */
abstract class TokenEmulator
{
abstract public function getPhpVersion(): string;
abstract public function isEmulationNeeded(string $code): bool;
/**
* @return array Modified Tokens
*/
abstract public function emulate(string $code, array $tokens): array;
/**
* @return array Modified Tokens
*/
abstract public function reverseEmulate(string $code, array $tokens): array;
public function preprocessCode(string $code, array &$patches): string {
return $code;
}
}

View File

@@ -95,8 +95,6 @@ interface Node
/**
* Gets the doc comment of the node.
*
* The doc comment has to be the last comment associated with the node.
*
* @return null|Comment\Doc Doc comment object or null
*/
public function getDocComment();

View File

@@ -2,10 +2,13 @@
namespace PhpParser\Node;
use PhpParser\Node\VariadicPlaceholder;
use PhpParser\NodeAbstract;
class Arg extends NodeAbstract
{
/** @var Identifier|null Parameter name (for named parameters) */
public $name;
/** @var Expr Value to pass */
public $value;
/** @var bool Whether to pass by ref */
@@ -20,16 +23,21 @@ class Arg extends NodeAbstract
* @param bool $byRef Whether to pass by ref
* @param bool $unpack Whether to unpack the argument
* @param array $attributes Additional attributes
* @param Identifier|null $name Parameter name (for named parameters)
*/
public function __construct(Expr $value, bool $byRef = false, bool $unpack = false, array $attributes = []) {
parent::__construct($attributes);
public function __construct(
Expr $value, bool $byRef = false, bool $unpack = false, array $attributes = [],
Identifier $name = null
) {
$this->attributes = $attributes;
$this->name = $name;
$this->value = $value;
$this->byRef = $byRef;
$this->unpack = $unpack;
}
public function getSubNodeNames() : array {
return ['value', 'byRef', 'unpack'];
return ['name', 'value', 'byRef', 'unpack'];
}
public function getType() : string {

View File

@@ -0,0 +1,34 @@
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\Node;
use PhpParser\NodeAbstract;
class Attribute extends NodeAbstract
{
/** @var Name Attribute name */
public $name;
/** @var Arg[] Attribute arguments */
public $args;
/**
* @param Node\Name $name Attribute name
* @param Arg[] $args Attribute arguments
* @param array $attributes Additional node attributes
*/
public function __construct(Name $name, array $args = [], array $attributes = []) {
$this->attributes = $attributes;
$this->name = $name;
$this->args = $args;
}
public function getSubNodeNames() : array {
return ['name', 'args'];
}
public function getType() : string {
return 'Attribute';
}
}

View File

@@ -0,0 +1,29 @@
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\Node;
use PhpParser\NodeAbstract;
class AttributeGroup extends NodeAbstract
{
/** @var Attribute[] Attributes */
public $attrs;
/**
* @param Attribute[] $attrs PHP attributes
* @param array $attributes Additional node attributes
*/
public function __construct(array $attrs, array $attributes = []) {
$this->attributes = $attributes;
$this->attrs = $attrs;
}
public function getSubNodeNames() : array {
return ['attrs'];
}
public function getType() : string {
return 'AttributeGroup';
}
}

View File

@@ -0,0 +1,14 @@
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\NodeAbstract;
/**
* This is a base class for complex types, including nullable types and union types.
*
* It does not provide any shared behavior and exists only for type-checking purposes.
*/
abstract class ComplexType extends NodeAbstract
{
}

View File

@@ -4,9 +4,6 @@ namespace PhpParser\Node;
use PhpParser\NodeAbstract;
/**
* @property Name $namespacedName Namespaced name (for class constants, if using NameResolver)
*/
class Const_ extends NodeAbstract
{
/** @var Identifier Name */
@@ -14,6 +11,9 @@ class Const_ extends NodeAbstract
/** @var Expr Value */
public $value;
/** @var Name|null Namespaced name (if using NameResolver) */
public $namespacedName;
/**
* Constructs a const node for use in class const and const statements.
*
@@ -22,7 +22,7 @@ class Const_ extends NodeAbstract
* @param array $attributes Additional attributes
*/
public function __construct($name, Expr $value, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->name = \is_string($name) ? new Identifier($name) : $name;
$this->value = $value;
}
@@ -30,7 +30,7 @@ class Const_ extends NodeAbstract
public function getSubNodeNames() : array {
return ['name', 'value'];
}
public function getType() : string {
return 'Const';
}

View File

@@ -19,7 +19,7 @@ class ArrayDimFetch extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, Expr $dim = null, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->var = $var;
$this->dim = $dim;
}

View File

@@ -12,6 +12,8 @@ class ArrayItem extends Expr
public $value;
/** @var bool Whether to assign by reference */
public $byRef;
/** @var bool Whether to unpack the argument */
public $unpack;
/**
* Constructs an array item node.
@@ -21,17 +23,18 @@ class ArrayItem extends Expr
* @param bool $byRef Whether to assign by reference
* @param array $attributes Additional attributes
*/
public function __construct(Expr $value, Expr $key = null, bool $byRef = false, array $attributes = []) {
parent::__construct($attributes);
public function __construct(Expr $value, Expr $key = null, bool $byRef = false, array $attributes = [], bool $unpack = false) {
$this->attributes = $attributes;
$this->key = $key;
$this->value = $value;
$this->byRef = $byRef;
$this->unpack = $unpack;
}
public function getSubNodeNames() : array {
return ['key', 'value', 'byRef'];
return ['key', 'value', 'byRef', 'unpack'];
}
public function getType() : string {
return 'Expr_ArrayItem';
}

View File

@@ -10,17 +10,17 @@ class Array_ extends Expr
const KIND_LONG = 1; // array() syntax
const KIND_SHORT = 2; // [] syntax
/** @var ArrayItem[] Items */
/** @var (ArrayItem|null)[] Items */
public $items;
/**
* Constructs an array node.
*
* @param ArrayItem[] $items Items of the array
* @param (ArrayItem|null)[] $items Items of the array
* @param array $attributes Additional attributes
*/
public function __construct(array $items = [], array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->items = $items;
}

View File

@@ -0,0 +1,79 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\FunctionLike;
class ArrowFunction extends Expr implements FunctionLike
{
/** @var bool */
public $static;
/** @var bool */
public $byRef;
/** @var Node\Param[] */
public $params = [];
/** @var null|Node\Identifier|Node\Name|Node\ComplexType */
public $returnType;
/** @var Expr */
public $expr;
/** @var Node\AttributeGroup[] */
public $attrGroups;
/**
* @param array $subNodes Array of the following optional subnodes:
* 'static' => false : Whether the closure is static
* 'byRef' => false : Whether to return by reference
* 'params' => array() : Parameters
* 'returnType' => null : Return type
* 'expr' => Expr : Expression body
* 'attrGroups' => array() : PHP attribute groups
* @param array $attributes Additional attributes
*/
public function __construct(array $subNodes = [], array $attributes = []) {
$this->attributes = $attributes;
$this->static = $subNodes['static'] ?? false;
$this->byRef = $subNodes['byRef'] ?? false;
$this->params = $subNodes['params'] ?? [];
$returnType = $subNodes['returnType'] ?? null;
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
$this->expr = $subNodes['expr'];
$this->attrGroups = $subNodes['attrGroups'] ?? [];
}
public function getSubNodeNames() : array {
return ['attrGroups', 'static', 'byRef', 'params', 'returnType', 'expr'];
}
public function returnsByRef() : bool {
return $this->byRef;
}
public function getParams() : array {
return $this->params;
}
public function getReturnType() {
return $this->returnType;
}
public function getAttrGroups() : array {
return $this->attrGroups;
}
/**
* @return Node\Stmt\Return_[]
*/
public function getStmts() : array {
return [new Node\Stmt\Return_($this->expr)];
}
public function getType() : string {
return 'Expr_ArrowFunction';
}
}

View File

@@ -19,7 +19,7 @@ class Assign extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, Expr $expr, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->var = $var;
$this->expr = $expr;
}

View File

@@ -19,7 +19,7 @@ abstract class AssignOp extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, Expr $expr, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->var = $var;
$this->expr = $expr;
}

View File

@@ -0,0 +1,12 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\AssignOp;
use PhpParser\Node\Expr\AssignOp;
class Coalesce extends AssignOp
{
public function getType() : string {
return 'Expr_AssignOp_Coalesce';
}
}

View File

@@ -19,7 +19,7 @@ class AssignRef extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, Expr $expr, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->var = $var;
$this->expr = $expr;
}

View File

@@ -19,7 +19,7 @@ abstract class BinaryOp extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $left, Expr $right, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->left = $left;
$this->right = $right;
}

View File

@@ -16,7 +16,7 @@ class BitwiseNot extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->expr = $expr;
}

View File

@@ -16,7 +16,7 @@ class BooleanNot extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->expr = $expr;
}

View File

@@ -0,0 +1,39 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\VariadicPlaceholder;
abstract class CallLike extends Expr {
/**
* Return raw arguments, which may be actual Args, or VariadicPlaceholders for first-class
* callables.
*
* @return array<Arg|VariadicPlaceholder>
*/
abstract public function getRawArgs(): array;
/**
* Returns whether this call expression is actually a first class callable.
*/
public function isFirstClassCallable(): bool {
foreach ($this->getRawArgs() as $arg) {
if ($arg instanceof VariadicPlaceholder) {
return true;
}
}
return false;
}
/**
* Assert that this is not a first-class callable and return only ordinary Args.
*
* @return Arg[]
*/
public function getArgs(): array {
assert(!$this->isFirstClassCallable());
return $this->getRawArgs();
}
}

View File

@@ -16,7 +16,7 @@ abstract class Cast extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->expr = $expr;
}

View File

@@ -6,6 +6,11 @@ use PhpParser\Node\Expr\Cast;
class Double extends Cast
{
// For use in "kind" attribute
const KIND_DOUBLE = 1; // "double" syntax
const KIND_FLOAT = 2; // "float" syntax
const KIND_REAL = 3; // "real" syntax
public function getType() : string {
return 'Expr_Cast_Double';
}

View File

@@ -21,7 +21,7 @@ class ClassConstFetch extends Expr
* @param array $attributes Additional attributes
*/
public function __construct($class, $name, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->class = $class;
$this->name = \is_string($name) ? new Identifier($name) : $name;
}

View File

@@ -16,7 +16,7 @@ class Clone_ extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->expr = $expr;
}

View File

@@ -16,10 +16,12 @@ class Closure extends Expr implements FunctionLike
public $params;
/** @var ClosureUse[] use()s */
public $uses;
/** @var null|Node\Identifier|Node\Name|Node\NullableType Return type */
/** @var null|Node\Identifier|Node\Name|Node\ComplexType Return type */
public $returnType;
/** @var Node\Stmt[] Statements */
public $stmts;
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/**
* Constructs a lambda function node.
@@ -31,10 +33,11 @@ class Closure extends Expr implements FunctionLike
* 'uses' => array(): use()s
* 'returnType' => null : Return type
* 'stmts' => array(): Statements
* 'attrGroups' => array(): PHP attributes groups
* @param array $attributes Additional attributes
*/
public function __construct(array $subNodes = [], array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->static = $subNodes['static'] ?? false;
$this->byRef = $subNodes['byRef'] ?? false;
$this->params = $subNodes['params'] ?? [];
@@ -42,10 +45,11 @@ class Closure extends Expr implements FunctionLike
$returnType = $subNodes['returnType'] ?? null;
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
$this->stmts = $subNodes['stmts'] ?? [];
$this->attrGroups = $subNodes['attrGroups'] ?? [];
}
public function getSubNodeNames() : array {
return ['static', 'byRef', 'params', 'uses', 'returnType', 'stmts'];
return ['attrGroups', 'static', 'byRef', 'params', 'uses', 'returnType', 'stmts'];
}
public function returnsByRef() : bool {
@@ -64,7 +68,11 @@ class Closure extends Expr implements FunctionLike
public function getStmts() : array {
return $this->stmts;
}
public function getAttrGroups() : array {
return $this->attrGroups;
}
public function getType() : string {
return 'Expr_Closure';
}

View File

@@ -19,7 +19,7 @@ class ClosureUse extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr\Variable $var, bool $byRef = false, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->var = $var;
$this->byRef = $byRef;
}

View File

@@ -17,7 +17,7 @@ class ConstFetch extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Name $name, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->name = $name;
}

View File

@@ -16,7 +16,7 @@ class Empty_ extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->expr = $expr;
}

View File

@@ -18,7 +18,7 @@ class Error extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
}
public function getSubNodeNames() : array {

View File

@@ -16,7 +16,7 @@ class ErrorSuppress extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->expr = $expr;
}

View File

@@ -16,7 +16,7 @@ class Eval_ extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->expr = $expr;
}

View File

@@ -20,7 +20,7 @@ class Exit_ extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr = null, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->expr = $expr;
}

View File

@@ -5,22 +5,22 @@ namespace PhpParser\Node\Expr;
use PhpParser\Node;
use PhpParser\Node\Expr;
class FuncCall extends Expr
class FuncCall extends CallLike
{
/** @var Node\Name|Expr Function name */
public $name;
/** @var Node\Arg[] Arguments */
/** @var array<Node\Arg|Node\VariadicPlaceholder> Arguments */
public $args;
/**
* Constructs a function call node.
*
* @param Node\Name|Expr $name Function name
* @param Node\Arg[] $args Arguments
* @param array $attributes Additional attributes
* @param Node\Name|Expr $name Function name
* @param array<Node\Arg|Node\VariadicPlaceholder> $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct($name, array $args = [], array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->name = $name;
$this->args = $args;
}
@@ -32,4 +32,8 @@ class FuncCall extends Expr
public function getType() : string {
return 'Expr_FuncCall';
}
public function getRawArgs(): array {
return $this->args;
}
}

View File

@@ -24,7 +24,7 @@ class Include_ extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, int $type, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->expr = $expr;
$this->type = $type;
}

View File

@@ -20,7 +20,7 @@ class Instanceof_ extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, $class, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->expr = $expr;
$this->class = $class;
}

View File

@@ -16,7 +16,7 @@ class Isset_ extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(array $vars, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->vars = $vars;
}

View File

@@ -16,7 +16,7 @@ class List_ extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(array $items, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->items = $items;
}

View File

@@ -0,0 +1,31 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node;
use PhpParser\Node\MatchArm;
class Match_ extends Node\Expr
{
/** @var Node\Expr */
public $cond;
/** @var MatchArm[] */
public $arms;
/**
* @param MatchArm[] $arms
*/
public function __construct(Node\Expr $cond, array $arms = [], array $attributes = []) {
$this->attributes = $attributes;
$this->cond = $cond;
$this->arms = $arms;
}
public function getSubNodeNames() : array {
return ['cond', 'arms'];
}
public function getType() : string {
return 'Expr_Match';
}
}

View File

@@ -5,26 +5,27 @@ namespace PhpParser\Node\Expr;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\VariadicPlaceholder;
class MethodCall extends Expr
class MethodCall extends CallLike
{
/** @var Expr Variable holding object */
public $var;
/** @var Identifier|Expr Method name */
public $name;
/** @var Arg[] Arguments */
/** @var array<Arg|VariadicPlaceholder> Arguments */
public $args;
/**
* Constructs a function call node.
*
* @param Expr $var Variable holding object
* @param string|Identifier|Expr $name Method name
* @param Arg[] $args Arguments
* @param array $attributes Additional attributes
* @param Expr $var Variable holding object
* @param string|Identifier|Expr $name Method name
* @param array<Arg|VariadicPlaceholder> $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, $name, array $args = [], array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->var = $var;
$this->name = \is_string($name) ? new Identifier($name) : $name;
$this->args = $args;
@@ -37,4 +38,8 @@ class MethodCall extends Expr
public function getType() : string {
return 'Expr_MethodCall';
}
public function getRawArgs(): array {
return $this->args;
}
}

View File

@@ -3,24 +3,26 @@
namespace PhpParser\Node\Expr;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\VariadicPlaceholder;
class New_ extends Expr
class New_ extends CallLike
{
/** @var Node\Name|Expr|Node\Stmt\Class_ Class name */
public $class;
/** @var Node\Arg[] Arguments */
/** @var array<Arg|VariadicPlaceholder> Arguments */
public $args;
/**
* Constructs a function call node.
*
* @param Node\Name|Expr|Node\Stmt\Class_ $class Class name (or class node for anonymous classes)
* @param Node\Arg[] $args Arguments
* @param array<Arg|VariadicPlaceholder> $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct($class, array $args = [], array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->class = $class;
$this->args = $args;
}
@@ -32,4 +34,8 @@ class New_ extends Expr
public function getType() : string {
return 'Expr_New';
}
public function getRawArgs(): array {
return $this->args;
}
}

View File

@@ -0,0 +1,45 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\VariadicPlaceholder;
class NullsafeMethodCall extends CallLike
{
/** @var Expr Variable holding object */
public $var;
/** @var Identifier|Expr Method name */
public $name;
/** @var array<Arg|VariadicPlaceholder> Arguments */
public $args;
/**
* Constructs a nullsafe method call node.
*
* @param Expr $var Variable holding object
* @param string|Identifier|Expr $name Method name
* @param array<Arg|VariadicPlaceholder> $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, $name, array $args = [], array $attributes = []) {
$this->attributes = $attributes;
$this->var = $var;
$this->name = \is_string($name) ? new Identifier($name) : $name;
$this->args = $args;
}
public function getSubNodeNames() : array {
return ['var', 'name', 'args'];
}
public function getType() : string {
return 'Expr_NullsafeMethodCall';
}
public function getRawArgs(): array {
return $this->args;
}
}

View File

@@ -0,0 +1,35 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
class NullsafePropertyFetch extends Expr
{
/** @var Expr Variable holding object */
public $var;
/** @var Identifier|Expr Property name */
public $name;
/**
* Constructs a nullsafe property fetch node.
*
* @param Expr $var Variable holding object
* @param string|Identifier|Expr $name Property name
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, $name, array $attributes = []) {
$this->attributes = $attributes;
$this->var = $var;
$this->name = \is_string($name) ? new Identifier($name) : $name;
}
public function getSubNodeNames() : array {
return ['var', 'name'];
}
public function getType() : string {
return 'Expr_NullsafePropertyFetch';
}
}

View File

@@ -16,7 +16,7 @@ class PostDec extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->var = $var;
}

View File

@@ -16,7 +16,7 @@ class PostInc extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->var = $var;
}

View File

@@ -16,7 +16,7 @@ class PreDec extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->var = $var;
}

View File

@@ -16,7 +16,7 @@ class PreInc extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->var = $var;
}

View File

@@ -16,7 +16,7 @@ class Print_ extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->expr = $expr;
}

View File

@@ -20,7 +20,7 @@ class PropertyFetch extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, $name, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->var = $var;
$this->name = \is_string($name) ? new Identifier($name) : $name;
}

View File

@@ -16,7 +16,7 @@ class ShellExec extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(array $parts, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->parts = $parts;
}

View File

@@ -3,28 +3,30 @@
namespace PhpParser\Node\Expr;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\VariadicPlaceholder;
class StaticCall extends Expr
class StaticCall extends CallLike
{
/** @var Node\Name|Expr Class name */
public $class;
/** @var Identifier|Expr Method name */
public $name;
/** @var Node\Arg[] Arguments */
/** @var array<Arg|VariadicPlaceholder> Arguments */
public $args;
/**
* Constructs a static method call node.
*
* @param Node\Name|Expr $class Class name
* @param string|Identifier|Expr $name Method name
* @param Node\Arg[] $args Arguments
* @param array $attributes Additional attributes
* @param Node\Name|Expr $class Class name
* @param string|Identifier|Expr $name Method name
* @param array<Arg|VariadicPlaceholder> $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct($class, $name, array $args = [], array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->class = $class;
$this->name = \is_string($name) ? new Identifier($name) : $name;
$this->args = $args;
@@ -37,4 +39,8 @@ class StaticCall extends Expr
public function getType() : string {
return 'Expr_StaticCall';
}
public function getRawArgs(): array {
return $this->args;
}
}

View File

@@ -21,7 +21,7 @@ class StaticPropertyFetch extends Expr
* @param array $attributes Additional attributes
*/
public function __construct($class, $name, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->class = $class;
$this->name = \is_string($name) ? new VarLikeIdentifier($name) : $name;
}

View File

@@ -22,7 +22,7 @@ class Ternary extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $cond, $if, Expr $else, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->cond = $cond;
$this->if = $if;
$this->else = $else;

View File

@@ -0,0 +1,30 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node;
class Throw_ extends Node\Expr
{
/** @var Node\Expr Expression */
public $expr;
/**
* Constructs a throw expression node.
*
* @param Node\Expr $expr Expression
* @param array $attributes Additional attributes
*/
public function __construct(Node\Expr $expr, array $attributes = []) {
$this->attributes = $attributes;
$this->expr = $expr;
}
public function getSubNodeNames() : array {
return ['expr'];
}
public function getType() : string {
return 'Expr_Throw';
}
}

View File

@@ -16,7 +16,7 @@ class UnaryMinus extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->expr = $expr;
}

View File

@@ -16,7 +16,7 @@ class UnaryPlus extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->expr = $expr;
}

View File

@@ -13,10 +13,10 @@ class Variable extends Expr
* Constructs a variable node.
*
* @param string|Expr $name Name
* @param array $attributes Additional attributes
* @param array $attributes Additional attributes
*/
public function __construct($name, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->name = $name;
}

View File

@@ -16,7 +16,7 @@ class YieldFrom extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->expr = $expr;
}

View File

@@ -19,7 +19,7 @@ class Yield_ extends Expr
* @param array $attributes Additional attributes
*/
public function __construct(Expr $value = null, Expr $key = null, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->key = $key;
$this->value = $value;
}

View File

@@ -16,21 +16,28 @@ interface FunctionLike extends Node
/**
* List of parameters
*
* @return Node\Param[]
* @return Param[]
*/
public function getParams() : array;
/**
* Get the declared return type or null
*
* @return null|Identifier|Node\Name|Node\NullableType
* @return null|Identifier|Name|ComplexType
*/
public function getReturnType();
/**
* The function body
*
* @return Node\Stmt[]|null
* @return Stmt[]|null
*/
public function getStmts();
/**
* Get PHP attribute groups.
*
* @return AttributeGroup[]
*/
public function getAttrGroups() : array;
}

View File

@@ -25,7 +25,7 @@ class Identifier extends NodeAbstract
* @param array $attributes Additional attributes
*/
public function __construct(string $name, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->name = $name;
}

View File

@@ -0,0 +1,30 @@
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\NodeAbstract;
class IntersectionType extends ComplexType
{
/** @var (Identifier|Name)[] Types */
public $types;
/**
* Constructs an intersection type.
*
* @param (Identifier|Name)[] $types Types
* @param array $attributes Additional attributes
*/
public function __construct(array $types, array $attributes = []) {
$this->attributes = $attributes;
$this->types = $types;
}
public function getSubNodeNames() : array {
return ['types'];
}
public function getType() : string {
return 'IntersectionType';
}
}

View File

@@ -0,0 +1,31 @@
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\Node;
use PhpParser\NodeAbstract;
class MatchArm extends NodeAbstract
{
/** @var null|Node\Expr[] */
public $conds;
/** @var Node\Expr */
public $body;
/**
* @param null|Node\Expr[] $conds
*/
public function __construct($conds, Node\Expr $body, array $attributes = []) {
$this->conds = $conds;
$this->body = $body;
$this->attributes = $attributes;
}
public function getSubNodeNames() : array {
return ['conds', 'body'];
}
public function getType() : string {
return 'MatchArm';
}
}

View File

@@ -6,9 +6,7 @@ use PhpParser\NodeAbstract;
class Name extends NodeAbstract
{
/**
* @var string[] Parts of the name
*/
/** @var string[] Parts of the name */
public $parts;
private static $specialClassNames = [
@@ -24,7 +22,7 @@ class Name extends NodeAbstract
* @param array $attributes Additional attributes
*/
public function __construct($name, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->parts = self::prepareName($name);
}
@@ -87,7 +85,7 @@ class Name extends NodeAbstract
}
/**
* Returns a string representation of the name itself, without taking taking the name type into
* Returns a string representation of the name itself, without taking the name type into
* account (e.g., not including a leading backslash for fully qualified names).
*
* @return string String representation
@@ -164,7 +162,7 @@ class Name extends NodeAbstract
$realLength = $numParts - $realOffset;
} else {
$realLength = $length < 0 ? $length + $numParts - $realOffset : $length;
if ($realLength < 0 || $realLength > $numParts) {
if ($realLength < 0 || $realLength > $numParts - $realOffset) {
throw new \OutOfBoundsException(sprintf('Length %d is out of bounds', $length));
}
}
@@ -237,7 +235,7 @@ class Name extends NodeAbstract
'Expected string, array of parts or Name instance'
);
}
public function getType() : string {
return 'Name';
}

View File

@@ -2,9 +2,7 @@
namespace PhpParser\Node;
use PhpParser\NodeAbstract;
class NullableType extends NodeAbstract
class NullableType extends ComplexType
{
/** @var Identifier|Name Type */
public $type;
@@ -16,7 +14,7 @@ class NullableType extends NodeAbstract
* @param array $attributes Additional attributes
*/
public function __construct($type, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->type = \is_string($type) ? new Identifier($type) : $type;
}

View File

@@ -6,7 +6,7 @@ use PhpParser\NodeAbstract;
class Param extends NodeAbstract
{
/** @var null|Identifier|Name|NullableType Typehint */
/** @var null|Identifier|Name|ComplexType Type declaration */
public $type;
/** @var bool Whether parameter is passed by reference */
public $byRef;
@@ -16,33 +16,44 @@ class Param extends NodeAbstract
public $var;
/** @var null|Expr Default value */
public $default;
/** @var int */
public $flags;
/** @var AttributeGroup[] PHP attribute groups */
public $attrGroups;
/**
* Constructs a parameter node.
*
* @param Expr\Variable|Expr\Error $var Parameter variable
* @param null|Expr $default Default value
* @param null|string|Name|NullableType $type Typehint
* @param bool $byRef Whether is passed by reference
* @param bool $variadic Whether this is a variadic argument
* @param array $attributes Additional attributes
* @param Expr\Variable|Expr\Error $var Parameter variable
* @param null|Expr $default Default value
* @param null|string|Identifier|Name|ComplexType $type Type declaration
* @param bool $byRef Whether is passed by reference
* @param bool $variadic Whether this is a variadic argument
* @param array $attributes Additional attributes
* @param int $flags Optional visibility flags
* @param AttributeGroup[] $attrGroups PHP attribute groups
*/
public function __construct(
$var, Expr $default = null, $type = null,
bool $byRef = false, bool $variadic = false, array $attributes = []
bool $byRef = false, bool $variadic = false,
array $attributes = [],
int $flags = 0,
array $attrGroups = []
) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->type = \is_string($type) ? new Identifier($type) : $type;
$this->byRef = $byRef;
$this->variadic = $variadic;
$this->var = $var;
$this->default = $default;
$this->flags = $flags;
$this->attrGroups = $attrGroups;
}
public function getSubNodeNames() : array {
return ['type', 'byRef', 'variadic', 'var', 'default'];
return ['attrGroups', 'flags', 'type', 'byRef', 'variadic', 'var', 'default'];
}
public function getType() : string {
return 'Param';
}

View File

@@ -16,7 +16,7 @@ class DNumber extends Scalar
* @param array $attributes Additional attributes
*/
public function __construct(float $value, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->value = $value;
}
@@ -24,6 +24,17 @@ class DNumber extends Scalar
return ['value'];
}
/**
* @param mixed[] $attributes
*/
public static function fromString(string $str, array $attributes = []): DNumber
{
$attributes['rawValue'] = $str;
$float = self::parse($str);
return new DNumber($float, $attributes);
}
/**
* @internal
*
@@ -34,13 +45,9 @@ class DNumber extends Scalar
* @return float The parsed number
*/
public static function parse(string $str) : float {
// if string contains any of .eE just cast it to float
if (false !== strpbrk($str, '.eE')) {
return (float) $str;
}
$str = str_replace('_', '', $str);
// otherwise it's an integer notation that overflowed into a float
// if it starts with 0 it's one of the special integer notations
// Check whether this is one of the special integer notations.
if ('0' === $str[0]) {
// hex
if ('x' === $str[1] || 'X' === $str[1]) {
@@ -52,16 +59,18 @@ class DNumber extends Scalar
return bindec($str);
}
// oct
// substr($str, 0, strcspn($str, '89')) cuts the string at the first invalid digit (8 or 9)
// so that only the digits before that are used
return octdec(substr($str, 0, strcspn($str, '89')));
// oct, but only if the string does not contain any of '.eE'.
if (false === strpbrk($str, '.eE')) {
// substr($str, 0, strcspn($str, '89')) cuts the string at the first invalid digit
// (8 or 9) so that only the digits before that are used.
return octdec(substr($str, 0, strcspn($str, '89')));
}
}
// dec
return (float) $str;
}
public function getType() : string {
return 'Scalar_DNumber';
}

View File

@@ -17,7 +17,7 @@ class Encapsed extends Scalar
* @param array $attributes Additional attributes
*/
public function __construct(array $parts, array $attributes = []) {
parent::__construct($attributes);
$this->attributes = $attributes;
$this->parts = $parts;
}

Some files were not shown because too many files have changed in this diff Show More