mirror of
https://gitlab.com/signalytic/client-external/streamline/streamline-emr.git
synced 2026-09-13 11:41:31 +00:00
resolved conflicts
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
PHP Parser
|
||||
==========
|
||||
|
||||
[](https://coveralls.io/github/nikic/PHP-Parser?branch=master)
|
||||
|
||||
This is a PHP parser written in PHP. Its purpose is to simplify static code analysis and
|
||||
manipulation.
|
||||
|
||||
[**Documentation for version 5.x**][doc_master] (current; for running on PHP >= 7.4; for parsing PHP 7.0 to PHP 8.4, with limited support for parsing PHP 5.x).
|
||||
|
||||
[Documentation for version 4.x][doc_4_x] (supported; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.3).
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
The main features provided by this library are:
|
||||
|
||||
* Parsing PHP 7, and PHP 8 code into an abstract syntax tree (AST).
|
||||
* Invalid code can be parsed into a partial AST.
|
||||
* The AST contains accurate location information.
|
||||
* Dumping the AST in human-readable form.
|
||||
* Converting an AST back to PHP code.
|
||||
* Formatting can be preserved for partially changed ASTs.
|
||||
* Infrastructure to traverse and modify ASTs.
|
||||
* Resolution of namespaced names.
|
||||
* Evaluation of constant expressions.
|
||||
* Builders to simplify AST construction for code generation.
|
||||
* Converting an AST into JSON and back.
|
||||
|
||||
Quick Start
|
||||
-----------
|
||||
|
||||
Install the library using [composer](https://getcomposer.org):
|
||||
|
||||
php composer.phar require nikic/php-parser
|
||||
|
||||
Parse some PHP code into an AST and dump the result in human-readable form:
|
||||
|
||||
```php
|
||||
<?php
|
||||
use PhpParser\Error;
|
||||
use PhpParser\NodeDumper;
|
||||
use PhpParser\ParserFactory;
|
||||
|
||||
$code = <<<'CODE'
|
||||
<?php
|
||||
|
||||
function test($foo)
|
||||
{
|
||||
var_dump($foo);
|
||||
}
|
||||
CODE;
|
||||
|
||||
$parser = (new ParserFactory())->createForNewestSupportedVersion();
|
||||
try {
|
||||
$ast = $parser->parse($code);
|
||||
} catch (Error $error) {
|
||||
echo "Parse error: {$error->getMessage()}\n";
|
||||
return;
|
||||
}
|
||||
|
||||
$dumper = new NodeDumper;
|
||||
echo $dumper->dump($ast) . "\n";
|
||||
```
|
||||
|
||||
This dumps an AST looking something like this:
|
||||
|
||||
```
|
||||
array(
|
||||
0: Stmt_Function(
|
||||
attrGroups: array(
|
||||
)
|
||||
byRef: false
|
||||
name: Identifier(
|
||||
name: test
|
||||
)
|
||||
params: array(
|
||||
0: Param(
|
||||
attrGroups: array(
|
||||
)
|
||||
flags: 0
|
||||
type: null
|
||||
byRef: false
|
||||
variadic: false
|
||||
var: Expr_Variable(
|
||||
name: foo
|
||||
)
|
||||
default: null
|
||||
)
|
||||
)
|
||||
returnType: null
|
||||
stmts: array(
|
||||
0: Stmt_Expression(
|
||||
expr: Expr_FuncCall(
|
||||
name: Name(
|
||||
name: var_dump
|
||||
)
|
||||
args: array(
|
||||
0: Arg(
|
||||
name: null
|
||||
value: Expr_Variable(
|
||||
name: foo
|
||||
)
|
||||
byRef: false
|
||||
unpack: false
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
Let's traverse the AST and perform some kind of modification. For example, drop all function bodies:
|
||||
|
||||
```php
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Function_;
|
||||
use PhpParser\NodeTraverser;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
|
||||
$traverser = new NodeTraverser();
|
||||
$traverser->addVisitor(new class extends NodeVisitorAbstract {
|
||||
public function enterNode(Node $node) {
|
||||
if ($node instanceof Function_) {
|
||||
// Clean out the function body
|
||||
$node->stmts = [];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$ast = $traverser->traverse($ast);
|
||||
echo $dumper->dump($ast) . "\n";
|
||||
```
|
||||
|
||||
This gives us an AST where the `Function_::$stmts` are empty:
|
||||
|
||||
```
|
||||
array(
|
||||
0: Stmt_Function(
|
||||
attrGroups: array(
|
||||
)
|
||||
byRef: false
|
||||
name: Identifier(
|
||||
name: test
|
||||
)
|
||||
params: array(
|
||||
0: Param(
|
||||
attrGroups: array(
|
||||
)
|
||||
type: null
|
||||
byRef: false
|
||||
variadic: false
|
||||
var: Expr_Variable(
|
||||
name: foo
|
||||
)
|
||||
default: null
|
||||
)
|
||||
)
|
||||
returnType: null
|
||||
stmts: array(
|
||||
)
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
Finally, we can convert the new AST back to PHP code:
|
||||
|
||||
```php
|
||||
use PhpParser\PrettyPrinter;
|
||||
|
||||
$prettyPrinter = new PrettyPrinter\Standard;
|
||||
echo $prettyPrinter->prettyPrintFile($ast);
|
||||
```
|
||||
|
||||
This gives us our original code, minus the `var_dump()` call inside the function:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
function test($foo)
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
For a more comprehensive introduction, see the documentation.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
1. [Introduction](doc/0_Introduction.markdown)
|
||||
2. [Usage of basic components](doc/2_Usage_of_basic_components.markdown)
|
||||
|
||||
Component documentation:
|
||||
|
||||
* [Walking the AST](doc/component/Walking_the_AST.markdown)
|
||||
* Node visitors
|
||||
* Modifying the AST from a visitor
|
||||
* Short-circuiting traversals
|
||||
* Interleaved visitors
|
||||
* Simple node finding API
|
||||
* Parent and sibling references
|
||||
* [Name resolution](doc/component/Name_resolution.markdown)
|
||||
* Name resolver options
|
||||
* Name resolution context
|
||||
* [Pretty printing](doc/component/Pretty_printing.markdown)
|
||||
* Converting AST back to PHP code
|
||||
* Customizing formatting
|
||||
* Formatting-preserving code transformations
|
||||
* [AST builders](doc/component/AST_builders.markdown)
|
||||
* Fluent builders for AST nodes
|
||||
* [Lexer](doc/component/Lexer.markdown)
|
||||
* Emulation
|
||||
* Tokens, positions and attributes
|
||||
* [Error handling](doc/component/Error_handling.markdown)
|
||||
* Column information for errors
|
||||
* Error recovery (parsing of syntactically incorrect code)
|
||||
* [Constant expression evaluation](doc/component/Constant_expression_evaluation.markdown)
|
||||
* Evaluating constant/property/etc initializers
|
||||
* Handling errors and unsupported expressions
|
||||
* [JSON representation](doc/component/JSON_representation.markdown)
|
||||
* JSON encoding and decoding of ASTs
|
||||
* [Performance](doc/component/Performance.markdown)
|
||||
* Disabling Xdebug
|
||||
* Reusing objects
|
||||
* Garbage collection impact
|
||||
* [Frequently asked questions](doc/component/FAQ.markdown)
|
||||
* Parent and sibling references
|
||||
|
||||
[doc_3_x]: https://github.com/nikic/PHP-Parser/tree/3.x/doc
|
||||
[doc_4_x]: https://github.com/nikic/PHP-Parser/tree/4.x/doc
|
||||
[doc_master]: https://github.com/nikic/PHP-Parser/tree/master/doc
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
"type": "library",
|
||||
"description": "A PHP parser written in PHP",
|
||||
"keywords": [
|
||||
"php",
|
||||
"parser"
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nikita Popov"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.4",
|
||||
"ext-tokenizer": "*",
|
||||
"ext-json": "*",
|
||||
"ext-ctype": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.0",
|
||||
"ircmaxell/php-yacc": "^0.0.7"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "5.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PhpParser\\": "lib/PhpParser"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"PhpParser\\": "test/PhpParser/"
|
||||
}
|
||||
},
|
||||
"bin": [
|
||||
"bin/php-parse"
|
||||
]
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Modifiers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Const_;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class ClassConst implements PhpParser\Builder {
|
||||
protected int $flags = 0;
|
||||
/** @var array<string, mixed> */
|
||||
protected array $attributes = [];
|
||||
/** @var list<Const_> */
|
||||
protected array $constants = [];
|
||||
|
||||
/** @var list<Node\AttributeGroup> */
|
||||
protected array $attributeGroups = [];
|
||||
/** @var Identifier|Node\Name|Node\ComplexType|null */
|
||||
protected ?Node $type = null;
|
||||
|
||||
/**
|
||||
* Creates a class constant builder
|
||||
*
|
||||
* @param string|Identifier $name Name
|
||||
* @param Node\Expr|bool|null|int|float|string|array|\UnitEnum $value Value
|
||||
*/
|
||||
public function __construct($name, $value) {
|
||||
$this->constants = [new Const_($name, BuilderHelpers::normalizeValue($value))];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add another constant to const group
|
||||
*
|
||||
* @param string|Identifier $name Name
|
||||
* @param Node\Expr|bool|null|int|float|string|array|\UnitEnum $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, Modifiers::PUBLIC);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the constant protected.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeProtected() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the constant private.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePrivate() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the constant final.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeFinal() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the constant type.
|
||||
*
|
||||
* @param string|Node\Name|Identifier|Node\ComplexType $type
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setType($type) {
|
||||
$this->type = BuilderHelpers::normalizeType($type);
|
||||
|
||||
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,
|
||||
$this->type
|
||||
);
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
<?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 {
|
||||
/** @var Identifier|string */
|
||||
protected $name;
|
||||
protected ?Node\Expr $value = null;
|
||||
/** @var array<string, mixed> */
|
||||
protected array $attributes = [];
|
||||
|
||||
/** @var list<Node\AttributeGroup> */
|
||||
protected array $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->attributeGroups,
|
||||
$this->attributes
|
||||
);
|
||||
}
|
||||
}
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Modifiers;
|
||||
use PhpParser\Node;
|
||||
|
||||
class Param implements PhpParser\Builder {
|
||||
protected string $name;
|
||||
protected ?Node\Expr $default = null;
|
||||
/** @var Node\Identifier|Node\Name|Node\ComplexType|null */
|
||||
protected ?Node $type = null;
|
||||
protected bool $byRef = false;
|
||||
protected int $flags = 0;
|
||||
protected bool $variadic = false;
|
||||
/** @var list<Node\AttributeGroup> */
|
||||
protected array $attributeGroups = [];
|
||||
|
||||
/**
|
||||
* Creates a parameter builder.
|
||||
*
|
||||
* @param string $name Name of the parameter
|
||||
*/
|
||||
public function __construct(string $name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets default value for the parameter.
|
||||
*
|
||||
* @param mixed $value Default value to use
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function setDefault($value) {
|
||||
$this->default = BuilderHelpers::normalizeValue($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets type for the parameter.
|
||||
*
|
||||
* @param string|Node\Name|Node\Identifier|Node\ComplexType $type Parameter type
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function setType($type) {
|
||||
$this->type = BuilderHelpers::normalizeType($type);
|
||||
if ($this->type == 'void') {
|
||||
throw new \LogicException('Parameter type cannot be void');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the parameter accept the value by reference.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeByRef() {
|
||||
$this->byRef = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the parameter variadic
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeVariadic() {
|
||||
$this->variadic = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the (promoted) parameter public.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePublic() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PUBLIC);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the (promoted) parameter protected.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeProtected() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the (promoted) parameter private.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePrivate() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the (promoted) parameter readonly.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeReadonly() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::READONLY);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives the promoted property private(set) visibility.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePrivateSet() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE_SET);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives the promoted property protected(set) visibility.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeProtectedSet() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED_SET);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an attribute group.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @return Node\Param The built parameter node
|
||||
*/
|
||||
public function getNode(): Node {
|
||||
return new Node\Param(
|
||||
new Node\Expr\Variable($this->name),
|
||||
$this->default, $this->type, $this->byRef, $this->variadic, [], $this->flags, $this->attributeGroups
|
||||
);
|
||||
}
|
||||
}
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Modifiers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\ComplexType;
|
||||
|
||||
class Property implements PhpParser\Builder {
|
||||
protected string $name;
|
||||
|
||||
protected int $flags = 0;
|
||||
|
||||
protected ?Node\Expr $default = null;
|
||||
/** @var array<string, mixed> */
|
||||
protected array $attributes = [];
|
||||
/** @var null|Identifier|Name|ComplexType */
|
||||
protected ?Node $type = null;
|
||||
/** @var list<Node\AttributeGroup> */
|
||||
protected array $attributeGroups = [];
|
||||
/** @var list<Node\PropertyHook> */
|
||||
protected array $hooks = [];
|
||||
|
||||
/**
|
||||
* Creates a property builder.
|
||||
*
|
||||
* @param string $name Name of the property
|
||||
*/
|
||||
public function __construct(string $name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the property public.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePublic() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PUBLIC);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the property protected.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeProtected() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the property private.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePrivate() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the property static.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeStatic() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::STATIC);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the property readonly.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeReadonly() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::READONLY);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the property abstract. Requires at least one property hook to be specified as well.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeAbstract() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::ABSTRACT);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the property final.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeFinal() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::FINAL);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives the property private(set) visibility.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePrivateSet() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE_SET);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives the property protected(set) visibility.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeProtectedSet() {
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED_SET);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets default value for the property.
|
||||
*
|
||||
* @param mixed $value Default value to use
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function setDefault($value) {
|
||||
$this->default = BuilderHelpers::normalizeValue($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets doc comment for the property.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a property hook.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function addHook(Node\PropertyHook $hook) {
|
||||
$this->hooks[] = $hook;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the built class node.
|
||||
*
|
||||
* @return Stmt\Property The built property node
|
||||
*/
|
||||
public function getNode(): PhpParser\Node {
|
||||
if ($this->flags & Modifiers::ABSTRACT && !$this->hooks) {
|
||||
throw new PhpParser\Error('Only hooked properties may be declared abstract');
|
||||
}
|
||||
|
||||
return new Stmt\Property(
|
||||
$this->flags !== 0 ? $this->flags : Modifiers::PUBLIC,
|
||||
[
|
||||
new Node\PropertyItem($this->name, $this->default)
|
||||
],
|
||||
$this->attributes,
|
||||
$this->type,
|
||||
$this->attributeGroups,
|
||||
$this->hooks
|
||||
);
|
||||
}
|
||||
}
|
||||
+375
@@ -0,0 +1,375 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\BinaryOp\Concat;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
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
|
||||
*/
|
||||
public function attribute($name, array $args = []): Node\Attribute {
|
||||
return new Node\Attribute(
|
||||
BuilderHelpers::normalizeName($name),
|
||||
$this->args($args)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a namespace builder.
|
||||
*
|
||||
* @param null|string|Node\Name $name Name of the namespace
|
||||
*
|
||||
* @return Builder\Namespace_ The created namespace builder
|
||||
*/
|
||||
public function namespace($name): Builder\Namespace_ {
|
||||
return new Builder\Namespace_($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a class builder.
|
||||
*
|
||||
* @param string $name Name of the class
|
||||
*
|
||||
* @return Builder\Class_ The created class builder
|
||||
*/
|
||||
public function class(string $name): Builder\Class_ {
|
||||
return new Builder\Class_($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an interface builder.
|
||||
*
|
||||
* @param string $name Name of the interface
|
||||
*
|
||||
* @return Builder\Interface_ The created interface builder
|
||||
*/
|
||||
public function interface(string $name): Builder\Interface_ {
|
||||
return new Builder\Interface_($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a trait builder.
|
||||
*
|
||||
* @param string $name Name of the trait
|
||||
*
|
||||
* @return Builder\Trait_ The created trait builder
|
||||
*/
|
||||
public function trait(string $name): Builder\Trait_ {
|
||||
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.
|
||||
*
|
||||
* @param Node\Name|string ...$traits Trait names
|
||||
*
|
||||
* @return Builder\TraitUse The created trait use builder
|
||||
*/
|
||||
public function useTrait(...$traits): Builder\TraitUse {
|
||||
return new Builder\TraitUse(...$traits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a trait use adaptation builder.
|
||||
*
|
||||
* @param Node\Name|string|null $trait Trait name
|
||||
* @param Node\Identifier|string $method Method name
|
||||
*
|
||||
* @return Builder\TraitUseAdaptation The created trait use adaptation builder
|
||||
*/
|
||||
public function traitUseAdaptation($trait, $method = null): Builder\TraitUseAdaptation {
|
||||
if ($method === null) {
|
||||
$method = $trait;
|
||||
$trait = null;
|
||||
}
|
||||
|
||||
return new Builder\TraitUseAdaptation($trait, $method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a method builder.
|
||||
*
|
||||
* @param string $name Name of the method
|
||||
*
|
||||
* @return Builder\Method The created method builder
|
||||
*/
|
||||
public function method(string $name): Builder\Method {
|
||||
return new Builder\Method($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a parameter builder.
|
||||
*
|
||||
* @param string $name Name of the parameter
|
||||
*
|
||||
* @return Builder\Param The created parameter builder
|
||||
*/
|
||||
public function param(string $name): Builder\Param {
|
||||
return new Builder\Param($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a property builder.
|
||||
*
|
||||
* @param string $name Name of the property
|
||||
*
|
||||
* @return Builder\Property The created property builder
|
||||
*/
|
||||
public function property(string $name): Builder\Property {
|
||||
return new Builder\Property($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function builder.
|
||||
*
|
||||
* @param string $name Name of the function
|
||||
*
|
||||
* @return Builder\Function_ The created function builder
|
||||
*/
|
||||
public function function(string $name): Builder\Function_ {
|
||||
return new Builder\Function_($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a namespace/class use builder.
|
||||
*
|
||||
* @param Node\Name|string $name Name of the entity (namespace or class) to alias
|
||||
*
|
||||
* @return Builder\Use_ The created use builder
|
||||
*/
|
||||
public function use($name): Builder\Use_ {
|
||||
return new Builder\Use_($name, Use_::TYPE_NORMAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function use builder.
|
||||
*
|
||||
* @param Node\Name|string $name Name of the function to alias
|
||||
*
|
||||
* @return Builder\Use_ The created use function builder
|
||||
*/
|
||||
public function useFunction($name): Builder\Use_ {
|
||||
return new Builder\Use_($name, Use_::TYPE_FUNCTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a constant use builder.
|
||||
*
|
||||
* @param Node\Name|string $name Name of the const to alias
|
||||
*
|
||||
* @return Builder\Use_ The created use const builder
|
||||
*/
|
||||
public function useConst($name): Builder\Use_ {
|
||||
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.
|
||||
*
|
||||
* @param Expr|bool|null|int|float|string|array|\UnitEnum $value $value
|
||||
*/
|
||||
public function val($value): Expr {
|
||||
return BuilderHelpers::normalizeValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates variable node.
|
||||
*
|
||||
* @param string|Expr $name Name
|
||||
*/
|
||||
public function var($name): Expr\Variable {
|
||||
if (!\is_string($name) && !$name instanceof Expr) {
|
||||
throw new \LogicException('Variable name must be string or Expr');
|
||||
}
|
||||
|
||||
return new Expr\Variable($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes an argument list.
|
||||
*
|
||||
* Creates Arg nodes for all arguments and converts literal values to expressions.
|
||||
*
|
||||
* @param array $args List of arguments to normalize
|
||||
*
|
||||
* @return list<Arg>
|
||||
*/
|
||||
public function args(array $args): array {
|
||||
$normalizedArgs = [];
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function call node.
|
||||
*
|
||||
* @param string|Name|Expr $name Function name
|
||||
* @param array $args Function arguments
|
||||
*/
|
||||
public function funcCall($name, array $args = []): Expr\FuncCall {
|
||||
return new Expr\FuncCall(
|
||||
BuilderHelpers::normalizeNameOrExpr($name),
|
||||
$this->args($args)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a method call node.
|
||||
*
|
||||
* @param Expr $var Variable the method is called on
|
||||
* @param string|Identifier|Expr $name Method name
|
||||
* @param array $args Method arguments
|
||||
*/
|
||||
public function methodCall(Expr $var, $name, array $args = []): Expr\MethodCall {
|
||||
return new Expr\MethodCall(
|
||||
$var,
|
||||
BuilderHelpers::normalizeIdentifierOrExpr($name),
|
||||
$this->args($args)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a static method call node.
|
||||
*
|
||||
* @param string|Name|Expr $class Class name
|
||||
* @param string|Identifier|Expr $name Method name
|
||||
* @param array $args Method arguments
|
||||
*/
|
||||
public function staticCall($class, $name, array $args = []): Expr\StaticCall {
|
||||
return new Expr\StaticCall(
|
||||
BuilderHelpers::normalizeNameOrExpr($class),
|
||||
BuilderHelpers::normalizeIdentifierOrExpr($name),
|
||||
$this->args($args)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an object creation node.
|
||||
*
|
||||
* @param string|Name|Expr $class Class name
|
||||
* @param array $args Constructor arguments
|
||||
*/
|
||||
public function new($class, array $args = []): Expr\New_ {
|
||||
return new Expr\New_(
|
||||
BuilderHelpers::normalizeNameOrExpr($class),
|
||||
$this->args($args)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a constant fetch node.
|
||||
*
|
||||
* @param string|Name $name Constant name
|
||||
*/
|
||||
public function constFetch($name): Expr\ConstFetch {
|
||||
return new Expr\ConstFetch(BuilderHelpers::normalizeName($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a property fetch node.
|
||||
*
|
||||
* @param Expr $var Variable holding object
|
||||
* @param string|Identifier|Expr $name Property name
|
||||
*/
|
||||
public function propertyFetch(Expr $var, $name): Expr\PropertyFetch {
|
||||
return new Expr\PropertyFetch($var, BuilderHelpers::normalizeIdentifierOrExpr($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a class constant fetch node.
|
||||
*
|
||||
* @param string|Name|Expr $class Class name
|
||||
* @param string|Identifier|Expr $name Constant name
|
||||
*/
|
||||
public function classConstFetch($class, $name): Expr\ClassConstFetch {
|
||||
return new Expr\ClassConstFetch(
|
||||
BuilderHelpers::normalizeNameOrExpr($class),
|
||||
BuilderHelpers::normalizeIdentifierOrExpr($name)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates nested Concat nodes from a list of expressions.
|
||||
*
|
||||
* @param Expr|string ...$exprs Expressions or literal strings
|
||||
*/
|
||||
public function concat(...$exprs): Concat {
|
||||
$numExprs = count($exprs);
|
||||
if ($numExprs < 2) {
|
||||
throw new \LogicException('Expected at least two expressions');
|
||||
}
|
||||
|
||||
$lastConcat = $this->normalizeStringExpr($exprs[0]);
|
||||
for ($i = 1; $i < $numExprs; $i++) {
|
||||
$lastConcat = new Concat($lastConcat, $this->normalizeStringExpr($exprs[$i]));
|
||||
}
|
||||
return $lastConcat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|Expr $expr
|
||||
*/
|
||||
private function normalizeStringExpr($expr): Expr {
|
||||
if ($expr instanceof Expr) {
|
||||
return $expr;
|
||||
}
|
||||
|
||||
if (\is_string($expr)) {
|
||||
return new String_($expr);
|
||||
}
|
||||
|
||||
throw new \LogicException('Expected string or Expr');
|
||||
}
|
||||
}
|
||||
+338
@@ -0,0 +1,338 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
use PhpParser\Node\ComplexType;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\Scalar;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
/**
|
||||
* This class defines helpers used in the implementation of builders. Don't use it directly.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class BuilderHelpers {
|
||||
/**
|
||||
* Normalizes a node: Converts builder objects to nodes.
|
||||
*
|
||||
* @param Node|Builder $node The node to normalize
|
||||
*
|
||||
* @return Node The normalized node
|
||||
*/
|
||||
public static function normalizeNode($node): Node {
|
||||
if ($node instanceof Builder) {
|
||||
return $node->getNode();
|
||||
}
|
||||
|
||||
if ($node instanceof Node) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
throw new \LogicException('Expected node or builder object');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a node to a statement.
|
||||
*
|
||||
* Expressions are wrapped in a Stmt\Expression node.
|
||||
*
|
||||
* @param Node|Builder $node The node to normalize
|
||||
*
|
||||
* @return Stmt The normalized statement node
|
||||
*/
|
||||
public static function normalizeStmt($node): Stmt {
|
||||
$node = self::normalizeNode($node);
|
||||
if ($node instanceof Stmt) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
if ($node instanceof Expr) {
|
||||
return new Stmt\Expression($node);
|
||||
}
|
||||
|
||||
throw new \LogicException('Expected statement or expression node');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes strings to Identifier.
|
||||
*
|
||||
* @param string|Identifier $name The identifier to normalize
|
||||
*
|
||||
* @return Identifier The normalized identifier
|
||||
*/
|
||||
public static function normalizeIdentifier($name): Identifier {
|
||||
if ($name instanceof Identifier) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
if (\is_string($name)) {
|
||||
return new Identifier($name);
|
||||
}
|
||||
|
||||
throw new \LogicException('Expected string or instance of Node\Identifier');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes strings to Identifier, also allowing expressions.
|
||||
*
|
||||
* @param string|Identifier|Expr $name The identifier to normalize
|
||||
*
|
||||
* @return Identifier|Expr The normalized identifier or expression
|
||||
*/
|
||||
public static function normalizeIdentifierOrExpr($name) {
|
||||
if ($name instanceof Identifier || $name instanceof Expr) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
if (\is_string($name)) {
|
||||
return new Identifier($name);
|
||||
}
|
||||
|
||||
throw new \LogicException('Expected string or instance of Node\Identifier or Node\Expr');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a name: Converts string names to Name nodes.
|
||||
*
|
||||
* @param Name|string $name The name to normalize
|
||||
*
|
||||
* @return Name The normalized name
|
||||
*/
|
||||
public static function normalizeName($name): Name {
|
||||
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');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a name: Converts string names to Name nodes, while also allowing expressions.
|
||||
*
|
||||
* @param Expr|Name|string $name The name to normalize
|
||||
*
|
||||
* @return Name|Expr The normalized name or expression
|
||||
*/
|
||||
public static function normalizeNameOrExpr($name) {
|
||||
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'
|
||||
);
|
||||
}
|
||||
|
||||
return self::normalizeName($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a type: Converts plain-text type names into proper AST representation.
|
||||
*
|
||||
* In particular, builtin types become Identifiers, custom types become Names and nullables
|
||||
* are wrapped in NullableType nodes.
|
||||
*
|
||||
* @param string|Name|Identifier|ComplexType $type The type to normalize
|
||||
*
|
||||
* @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 ComplexType
|
||||
) {
|
||||
throw new \LogicException(
|
||||
'Type must be a string, or an instance of Name, Identifier or ComplexType'
|
||||
);
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
|
||||
$nullable = false;
|
||||
if (strlen($type) > 0 && $type[0] === '?') {
|
||||
$nullable = true;
|
||||
$type = substr($type, 1);
|
||||
}
|
||||
|
||||
$builtinTypes = [
|
||||
'array',
|
||||
'callable',
|
||||
'bool',
|
||||
'int',
|
||||
'float',
|
||||
'string',
|
||||
'iterable',
|
||||
'void',
|
||||
'object',
|
||||
'null',
|
||||
'false',
|
||||
'mixed',
|
||||
'never',
|
||||
'true',
|
||||
];
|
||||
|
||||
$lowerType = strtolower($type);
|
||||
if (in_array($lowerType, $builtinTypes)) {
|
||||
$type = new Identifier($lowerType);
|
||||
} else {
|
||||
$type = self::normalizeName($type);
|
||||
}
|
||||
|
||||
$notNullableTypes = [
|
||||
'void', 'mixed', 'never',
|
||||
];
|
||||
if ($nullable && in_array((string) $type, $notNullableTypes)) {
|
||||
throw new \LogicException(sprintf('%s type cannot be nullable', $type));
|
||||
}
|
||||
|
||||
return $nullable ? new NullableType($type) : $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a value: Converts nulls, booleans, integers,
|
||||
* floats, strings and arrays into their respective nodes
|
||||
*
|
||||
* @param Node\Expr|bool|null|int|float|string|array|\UnitEnum $value The value to normalize
|
||||
*
|
||||
* @return Expr The normalized value
|
||||
*/
|
||||
public static function normalizeValue($value): Expr {
|
||||
if ($value instanceof Node\Expr) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (is_null($value)) {
|
||||
return new Expr\ConstFetch(
|
||||
new Name('null')
|
||||
);
|
||||
}
|
||||
|
||||
if (is_bool($value)) {
|
||||
return new Expr\ConstFetch(
|
||||
new Name($value ? 'true' : 'false')
|
||||
);
|
||||
}
|
||||
|
||||
if (is_int($value)) {
|
||||
return new Scalar\Int_($value);
|
||||
}
|
||||
|
||||
if (is_float($value)) {
|
||||
return new Scalar\Float_($value);
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
return new Scalar\String_($value);
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
$items = [];
|
||||
$lastKey = -1;
|
||||
foreach ($value as $itemKey => $itemValue) {
|
||||
// for consecutive, numeric keys don't generate keys
|
||||
if (null !== $lastKey && ++$lastKey === $itemKey) {
|
||||
$items[] = new Node\ArrayItem(
|
||||
self::normalizeValue($itemValue)
|
||||
);
|
||||
} else {
|
||||
$lastKey = null;
|
||||
$items[] = new Node\ArrayItem(
|
||||
self::normalizeValue($itemValue),
|
||||
self::normalizeValue($itemKey)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new Expr\Array_($items);
|
||||
}
|
||||
|
||||
if ($value instanceof \UnitEnum) {
|
||||
return new Expr\ClassConstFetch(new FullyQualified(\get_class($value)), new Identifier($value->name));
|
||||
}
|
||||
|
||||
throw new \LogicException('Invalid value');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a doc comment: Converts plain strings to PhpParser\Comment\Doc.
|
||||
*
|
||||
* @param Comment\Doc|string $docComment The doc comment to normalize
|
||||
*
|
||||
* @return Comment\Doc The normalized doc comment
|
||||
*/
|
||||
public static function normalizeDocComment($docComment): Comment\Doc {
|
||||
if ($docComment instanceof Comment\Doc) {
|
||||
return $docComment;
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a modifier and returns new modifier bitmask.
|
||||
*
|
||||
* @param int $modifiers Existing modifiers
|
||||
* @param int $modifier Modifier to set
|
||||
*
|
||||
* @return int New modifiers
|
||||
*/
|
||||
public static function addModifier(int $modifiers, int $modifier): int {
|
||||
Modifiers::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 {
|
||||
Modifiers::verifyClassModifier($existingModifiers, $modifierToSet);
|
||||
return $existingModifiers | $modifierToSet;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
class Comment implements \JsonSerializable {
|
||||
protected string $text;
|
||||
protected int $startLine;
|
||||
protected int $startFilePos;
|
||||
protected int $startTokenPos;
|
||||
protected int $endLine;
|
||||
protected int $endFilePos;
|
||||
protected int $endTokenPos;
|
||||
|
||||
/**
|
||||
* Constructs a comment node.
|
||||
*
|
||||
* @param string $text Comment text (including comment delimiters like /*)
|
||||
* @param int $startLine Line number the comment started on
|
||||
* @param int $startFilePos File offset the comment started on
|
||||
* @param int $startTokenPos Token offset the comment started on
|
||||
*/
|
||||
public function __construct(
|
||||
string $text,
|
||||
int $startLine = -1, int $startFilePos = -1, int $startTokenPos = -1,
|
||||
int $endLine = -1, int $endFilePos = -1, int $endTokenPos = -1
|
||||
) {
|
||||
$this->text = $text;
|
||||
$this->startLine = $startLine;
|
||||
$this->startFilePos = $startFilePos;
|
||||
$this->startTokenPos = $startTokenPos;
|
||||
$this->endLine = $endLine;
|
||||
$this->endFilePos = $endFilePos;
|
||||
$this->endTokenPos = $endTokenPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the comment text.
|
||||
*
|
||||
* @return string The comment text (including comment delimiters like /*)
|
||||
*/
|
||||
public function getText(): string {
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line number the comment started on.
|
||||
*
|
||||
* @return int Line number (or -1 if not available)
|
||||
* @phpstan-return -1|positive-int
|
||||
*/
|
||||
public function getStartLine(): int {
|
||||
return $this->startLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file offset the comment started on.
|
||||
*
|
||||
* @return int File offset (or -1 if not available)
|
||||
*/
|
||||
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)
|
||||
* @phpstan-return -1|positive-int
|
||||
*/
|
||||
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 comment text.
|
||||
*
|
||||
* @return string The comment text (including comment delimiters like /*)
|
||||
*/
|
||||
public function __toString(): string {
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the reformatted comment text.
|
||||
*
|
||||
* "Reformatted" here means that we try to clean up the whitespace at the
|
||||
* starts of the lines. This is necessary because we receive the comments
|
||||
* without leading whitespace on the first line, but with leading whitespace
|
||||
* on all subsequent lines.
|
||||
*
|
||||
* Additionally, this normalizes CRLF newlines to LF newlines.
|
||||
*/
|
||||
public function getReformattedText(): string {
|
||||
$text = str_replace("\r\n", "\n", $this->text);
|
||||
$newlinePos = strpos($text, "\n");
|
||||
if (false === $newlinePos) {
|
||||
// Single line comments don't need further processing
|
||||
return $text;
|
||||
}
|
||||
if (preg_match('(^.*(?:\n\s+\*.*)+$)', $text)) {
|
||||
// Multi line comment of the type
|
||||
//
|
||||
// /*
|
||||
// * Some text.
|
||||
// * Some more text.
|
||||
// */
|
||||
//
|
||||
// is handled by replacing the whitespace sequences before the * by a single space
|
||||
return preg_replace('(^\s+\*)m', ' *', $text);
|
||||
}
|
||||
if (preg_match('(^/\*\*?\s*\n)', $text) && preg_match('(\n(\s*)\*/$)', $text, $matches)) {
|
||||
// Multi line comment of the type
|
||||
//
|
||||
// /*
|
||||
// Some text.
|
||||
// Some more text.
|
||||
// */
|
||||
//
|
||||
// is handled by removing the whitespace sequence on the line before the closing
|
||||
// */ on all lines. So if the last line is " */", then " " is removed at the
|
||||
// start of all lines.
|
||||
return preg_replace('(^' . preg_quote($matches[1]) . ')m', '', $text);
|
||||
}
|
||||
if (preg_match('(^/\*\*?\s*(?!\s))', $text, $matches)) {
|
||||
// Multi line comment of the type
|
||||
//
|
||||
// /* Some text.
|
||||
// Some more text.
|
||||
// Indented text.
|
||||
// Even more text. */
|
||||
//
|
||||
// is handled by removing the difference between the shortest whitespace prefix on all
|
||||
// lines and the length of the "/* " opening sequence.
|
||||
$prefixLen = $this->getShortestWhitespacePrefixLen(substr($text, $newlinePos + 1));
|
||||
$removeLen = $prefixLen - strlen($matches[0]);
|
||||
return preg_replace('(^\s{' . $removeLen . '})m', '', $text);
|
||||
}
|
||||
|
||||
// No idea how to format this comment, so simply return as is
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get length of shortest whitespace prefix (at the start of a line).
|
||||
*
|
||||
* If there is a line with no prefix whitespace, 0 is a valid return value.
|
||||
*
|
||||
* @param string $str String to check
|
||||
* @return int Length in characters. Tabs count as single characters.
|
||||
*/
|
||||
private function getShortestWhitespacePrefixLen(string $str): int {
|
||||
$lines = explode("\n", $str);
|
||||
$shortestPrefixLen = \PHP_INT_MAX;
|
||||
foreach ($lines as $line) {
|
||||
preg_match('(^\s*)', $line, $matches);
|
||||
$prefixLen = strlen($matches[0]);
|
||||
if ($prefixLen < $shortestPrefixLen) {
|
||||
$shortestPrefixLen = $prefixLen;
|
||||
}
|
||||
}
|
||||
return $shortestPrefixLen;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{nodeType:string, text:mixed, line:mixed, filePos:mixed}
|
||||
*/
|
||||
public function jsonSerialize(): array {
|
||||
// Technically not a node, but we make it look like one anyway
|
||||
$type = $this instanceof Comment\Doc ? 'Comment_Doc' : 'Comment';
|
||||
return [
|
||||
'nodeType' => $type,
|
||||
'text' => $this->text,
|
||||
// TODO: Rename these to include "start".
|
||||
'line' => $this->startLine,
|
||||
'filePos' => $this->startFilePos,
|
||||
'tokenPos' => $this->startTokenPos,
|
||||
'endLine' => $this->endLine,
|
||||
'endFilePos' => $this->endFilePos,
|
||||
'endTokenPos' => $this->endTokenPos,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
class Error extends \RuntimeException {
|
||||
protected string $rawMessage;
|
||||
/** @var array<string, mixed> */
|
||||
protected array $attributes;
|
||||
|
||||
/**
|
||||
* Creates an Exception signifying a parse error.
|
||||
*
|
||||
* @param string $message Error message
|
||||
* @param array<string, mixed> $attributes Attributes of node/token where error occurred
|
||||
*/
|
||||
public function __construct(string $message, array $attributes = []) {
|
||||
$this->rawMessage = $message;
|
||||
$this->attributes = $attributes;
|
||||
$this->updateMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the error message
|
||||
*
|
||||
* @return string Error message
|
||||
*/
|
||||
public function getRawMessage(): string {
|
||||
return $this->rawMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line the error starts in.
|
||||
*
|
||||
* @return int Error start line
|
||||
* @phpstan-return -1|positive-int
|
||||
*/
|
||||
public function getStartLine(): int {
|
||||
return $this->attributes['startLine'] ?? -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line the error ends in.
|
||||
*
|
||||
* @return int Error end line
|
||||
* @phpstan-return -1|positive-int
|
||||
*/
|
||||
public function getEndLine(): int {
|
||||
return $this->attributes['endLine'] ?? -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the attributes of the node/token the error occurred at.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getAttributes(): array {
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the attributes of the node/token the error occurred at.
|
||||
*
|
||||
* @param array<string, mixed> $attributes
|
||||
*/
|
||||
public function setAttributes(array $attributes): void {
|
||||
$this->attributes = $attributes;
|
||||
$this->updateMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the line of the PHP file the error occurred in.
|
||||
*
|
||||
* @param string $message Error message
|
||||
*/
|
||||
public function setRawMessage(string $message): void {
|
||||
$this->rawMessage = $message;
|
||||
$this->updateMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the line the error starts in.
|
||||
*
|
||||
* @param int $line Error start line
|
||||
*/
|
||||
public function setStartLine(int $line): void {
|
||||
$this->attributes['startLine'] = $line;
|
||||
$this->updateMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the error has start and end column information.
|
||||
*
|
||||
* For column information enable the startFilePos and endFilePos in the lexer options.
|
||||
*/
|
||||
public function hasColumnInfo(): bool {
|
||||
return isset($this->attributes['startFilePos'], $this->attributes['endFilePos']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the start column (1-based) into the line where the error started.
|
||||
*
|
||||
* @param string $code Source code of the file
|
||||
*/
|
||||
public function getStartColumn(string $code): int {
|
||||
if (!$this->hasColumnInfo()) {
|
||||
throw new \RuntimeException('Error does not have column information');
|
||||
}
|
||||
|
||||
return $this->toColumn($code, $this->attributes['startFilePos']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the end column (1-based) into the line where the error ended.
|
||||
*
|
||||
* @param string $code Source code of the file
|
||||
*/
|
||||
public function getEndColumn(string $code): int {
|
||||
if (!$this->hasColumnInfo()) {
|
||||
throw new \RuntimeException('Error does not have column information');
|
||||
}
|
||||
|
||||
return $this->toColumn($code, $this->attributes['endFilePos']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats message including line and column information.
|
||||
*
|
||||
* @param string $code Source code associated with the error, for calculation of the columns
|
||||
*
|
||||
* @return string Formatted message
|
||||
*/
|
||||
public function getMessageWithColumnInfo(string $code): string {
|
||||
return sprintf(
|
||||
'%s from %d:%d to %d:%d', $this->getRawMessage(),
|
||||
$this->getStartLine(), $this->getStartColumn($code),
|
||||
$this->getEndLine(), $this->getEndColumn($code)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a file offset into a column.
|
||||
*
|
||||
* @param string $code Source code that $pos indexes into
|
||||
* @param int $pos 0-based position in $code
|
||||
*
|
||||
* @return int 1-based column (relative to start of line)
|
||||
*/
|
||||
private function toColumn(string $code, int $pos): int {
|
||||
if ($pos > strlen($code)) {
|
||||
throw new \RuntimeException('Invalid position information');
|
||||
}
|
||||
|
||||
$lineStartPos = strrpos($code, "\n", $pos - strlen($code));
|
||||
if (false === $lineStartPos) {
|
||||
$lineStartPos = -1;
|
||||
}
|
||||
|
||||
return $pos - $lineStartPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the exception message after a change to rawMessage or rawLine.
|
||||
*/
|
||||
protected function updateMessage(): void {
|
||||
$this->message = $this->rawMessage;
|
||||
|
||||
if (-1 === $this->getStartLine()) {
|
||||
$this->message .= ' on unknown line';
|
||||
} else {
|
||||
$this->message .= ' on line ' . $this->getStartLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
+282
@@ -0,0 +1,282 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Internal;
|
||||
|
||||
use PhpParser\Token;
|
||||
|
||||
/**
|
||||
* Provides operations on token streams, for use by pretty printer.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class TokenStream {
|
||||
/** @var Token[] Tokens (in PhpToken::tokenize() format) */
|
||||
private array $tokens;
|
||||
/** @var int[] Map from position to indentation */
|
||||
private array $indentMap;
|
||||
|
||||
/**
|
||||
* Create token stream instance.
|
||||
*
|
||||
* @param Token[] $tokens Tokens in PhpToken::tokenize() format
|
||||
*/
|
||||
public function __construct(array $tokens, int $tabWidth) {
|
||||
$this->tokens = $tokens;
|
||||
$this->indentMap = $this->calcIndentMap($tabWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given position is immediately surrounded by parenthesis.
|
||||
*
|
||||
* @param int $startPos Start position
|
||||
* @param int $endPos End position
|
||||
*/
|
||||
public function haveParens(int $startPos, int $endPos): bool {
|
||||
return $this->haveTokenImmediatelyBefore($startPos, '(')
|
||||
&& $this->haveTokenImmediatelyAfter($endPos, ')');
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given position is immediately surrounded by braces.
|
||||
*
|
||||
* @param int $startPos Start position
|
||||
* @param int $endPos End position
|
||||
*/
|
||||
public function haveBraces(int $startPos, int $endPos): bool {
|
||||
return ($this->haveTokenImmediatelyBefore($startPos, '{')
|
||||
|| $this->haveTokenImmediatelyBefore($startPos, T_CURLY_OPEN))
|
||||
&& $this->haveTokenImmediatelyAfter($endPos, '}');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the position is directly preceded by a certain token type.
|
||||
*
|
||||
* During this check whitespace and comments are skipped.
|
||||
*
|
||||
* @param int $pos Position before which the token should occur
|
||||
* @param int|string $expectedTokenType Token to check for
|
||||
*
|
||||
* @return bool Whether the expected token was found
|
||||
*/
|
||||
public function haveTokenImmediatelyBefore(int $pos, $expectedTokenType): bool {
|
||||
$tokens = $this->tokens;
|
||||
$pos--;
|
||||
for (; $pos >= 0; $pos--) {
|
||||
$token = $tokens[$pos];
|
||||
if ($token->is($expectedTokenType)) {
|
||||
return true;
|
||||
}
|
||||
if (!$token->isIgnorable()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the position is directly followed by a certain token type.
|
||||
*
|
||||
* During this check whitespace and comments are skipped.
|
||||
*
|
||||
* @param int $pos Position after which the token should occur
|
||||
* @param int|string $expectedTokenType Token to check for
|
||||
*
|
||||
* @return bool Whether the expected token was found
|
||||
*/
|
||||
public function haveTokenImmediatelyAfter(int $pos, $expectedTokenType): bool {
|
||||
$tokens = $this->tokens;
|
||||
$pos++;
|
||||
for ($c = \count($tokens); $pos < $c; $pos++) {
|
||||
$token = $tokens[$pos];
|
||||
if ($token->is($expectedTokenType)) {
|
||||
return true;
|
||||
}
|
||||
if (!$token->isIgnorable()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @param int|string|(int|string)[] $skipTokenType */
|
||||
public function skipLeft(int $pos, $skipTokenType): int {
|
||||
$tokens = $this->tokens;
|
||||
|
||||
$pos = $this->skipLeftWhitespace($pos);
|
||||
if ($skipTokenType === \T_WHITESPACE) {
|
||||
return $pos;
|
||||
}
|
||||
|
||||
if (!$tokens[$pos]->is($skipTokenType)) {
|
||||
// Shouldn't happen. The skip token MUST be there
|
||||
throw new \Exception('Encountered unexpected token');
|
||||
}
|
||||
$pos--;
|
||||
|
||||
return $this->skipLeftWhitespace($pos);
|
||||
}
|
||||
|
||||
/** @param int|string|(int|string)[] $skipTokenType */
|
||||
public function skipRight(int $pos, $skipTokenType): int {
|
||||
$tokens = $this->tokens;
|
||||
|
||||
$pos = $this->skipRightWhitespace($pos);
|
||||
if ($skipTokenType === \T_WHITESPACE) {
|
||||
return $pos;
|
||||
}
|
||||
|
||||
if (!$tokens[$pos]->is($skipTokenType)) {
|
||||
// Shouldn't happen. The skip token MUST be there
|
||||
throw new \Exception('Encountered unexpected token');
|
||||
}
|
||||
$pos++;
|
||||
|
||||
return $this->skipRightWhitespace($pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return first non-whitespace token position smaller or equal to passed position.
|
||||
*
|
||||
* @param int $pos Token position
|
||||
* @return int Non-whitespace token position
|
||||
*/
|
||||
public function skipLeftWhitespace(int $pos): int {
|
||||
$tokens = $this->tokens;
|
||||
for (; $pos >= 0; $pos--) {
|
||||
if (!$tokens[$pos]->isIgnorable()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return first non-whitespace position greater or equal to passed position.
|
||||
*
|
||||
* @param int $pos Token position
|
||||
* @return int Non-whitespace token position
|
||||
*/
|
||||
public function skipRightWhitespace(int $pos): int {
|
||||
$tokens = $this->tokens;
|
||||
for ($count = \count($tokens); $pos < $count; $pos++) {
|
||||
if (!$tokens[$pos]->isIgnorable()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $pos;
|
||||
}
|
||||
|
||||
/** @param int|string|(int|string)[] $findTokenType */
|
||||
public function findRight(int $pos, $findTokenType): int {
|
||||
$tokens = $this->tokens;
|
||||
for ($count = \count($tokens); $pos < $count; $pos++) {
|
||||
if ($tokens[$pos]->is($findTokenType)) {
|
||||
return $pos;
|
||||
}
|
||||
}
|
||||
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): bool {
|
||||
$tokens = $this->tokens;
|
||||
for ($pos = $startPos; $pos < $endPos; $pos++) {
|
||||
if ($tokens[$pos]->is($tokenType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* @param int $pos Token position
|
||||
*
|
||||
* @return int Indentation depth (in spaces)
|
||||
*/
|
||||
public function getIndentationBefore(int $pos): int {
|
||||
return $this->indentMap[$pos];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the code corresponding to a token offset range, optionally adjusted for indentation.
|
||||
*
|
||||
* @param int $from Token start position (inclusive)
|
||||
* @param int $to Token end position (exclusive)
|
||||
* @param int $indent By how much the code should be indented (can be negative as well)
|
||||
*
|
||||
* @return string Code corresponding to token range, adjusted for indentation
|
||||
*/
|
||||
public function getTokenCode(int $from, int $to, int $indent): string {
|
||||
$tokens = $this->tokens;
|
||||
$result = '';
|
||||
for ($pos = $from; $pos < $to; $pos++) {
|
||||
$token = $tokens[$pos];
|
||||
$id = $token->id;
|
||||
$text = $token->text;
|
||||
if ($id === \T_CONSTANT_ENCAPSED_STRING || $id === \T_ENCAPSED_AND_WHITESPACE) {
|
||||
$result .= $text;
|
||||
} else {
|
||||
// TODO Handle non-space indentation
|
||||
if ($indent < 0) {
|
||||
$result .= str_replace("\n" . str_repeat(" ", -$indent), "\n", $text);
|
||||
} elseif ($indent > 0) {
|
||||
$result .= str_replace("\n", "\n" . str_repeat(" ", $indent), $text);
|
||||
} else {
|
||||
$result .= $text;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Precalculate the indentation at every token position.
|
||||
*
|
||||
* @return int[] Token position to indentation map
|
||||
*/
|
||||
private function calcIndentMap(int $tabWidth): array {
|
||||
$indentMap = [];
|
||||
$indent = 0;
|
||||
foreach ($this->tokens as $i => $token) {
|
||||
$indentMap[] = $indent;
|
||||
|
||||
if ($token->id === \T_WHITESPACE) {
|
||||
$content = $token->text;
|
||||
$newlinePos = \strrpos($content, "\n");
|
||||
if (false !== $newlinePos) {
|
||||
$indent = $this->getIndent(\substr($content, $newlinePos + 1), $tabWidth);
|
||||
} elseif ($i === 1 && $this->tokens[0]->id === \T_OPEN_TAG &&
|
||||
$this->tokens[0]->text[\strlen($this->tokens[0]->text) - 1] === "\n") {
|
||||
// Special case: Newline at the end of opening tag followed by whitespace.
|
||||
$indent = $this->getIndent($content, $tabWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a sentinel for one past end of the file
|
||||
$indentMap[] = $indent;
|
||||
|
||||
return $indentMap;
|
||||
}
|
||||
|
||||
private function getIndent(string $ws, int $tabWidth): int {
|
||||
$spaces = \substr_count($ws, " ");
|
||||
$tabs = \substr_count($ws, "\t");
|
||||
assert(\strlen($ws) === $spaces + $tabs);
|
||||
return $spaces + $tabs * $tabWidth;
|
||||
}
|
||||
}
|
||||
+226
@@ -0,0 +1,226 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Lexer;
|
||||
|
||||
use PhpParser\Error;
|
||||
use PhpParser\ErrorHandler;
|
||||
use PhpParser\Lexer;
|
||||
use PhpParser\Lexer\TokenEmulator\AsymmetricVisibilityTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\AttributeEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\EnumTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\ExplicitOctalEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\PropertyTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\ReadonlyFunctionTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\ReadonlyTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\ReverseEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\TokenEmulator;
|
||||
use PhpParser\PhpVersion;
|
||||
use PhpParser\Token;
|
||||
|
||||
class Emulative extends Lexer {
|
||||
/** @var array{int, string, string}[] Patches used to reverse changes introduced in the code */
|
||||
private array $patches = [];
|
||||
|
||||
/** @var list<TokenEmulator> */
|
||||
private array $emulators = [];
|
||||
|
||||
private PhpVersion $targetPhpVersion;
|
||||
|
||||
private PhpVersion $hostPhpVersion;
|
||||
|
||||
/**
|
||||
* @param PhpVersion|null $phpVersion PHP version to emulate. Defaults to newest supported.
|
||||
*/
|
||||
public function __construct(?PhpVersion $phpVersion = null) {
|
||||
$this->targetPhpVersion = $phpVersion ?? PhpVersion::getNewestSupported();
|
||||
$this->hostPhpVersion = PhpVersion::getHostVersion();
|
||||
|
||||
$emulators = [
|
||||
new MatchTokenEmulator(),
|
||||
new NullsafeTokenEmulator(),
|
||||
new AttributeEmulator(),
|
||||
new EnumTokenEmulator(),
|
||||
new ReadonlyTokenEmulator(),
|
||||
new ExplicitOctalEmulator(),
|
||||
new ReadonlyFunctionTokenEmulator(),
|
||||
new PropertyTokenEmulator(),
|
||||
new AsymmetricVisibilityTokenEmulator(),
|
||||
];
|
||||
|
||||
// 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;
|
||||
} elseif ($this->isReverseEmulationNeeded($emulatorPhpVersion)) {
|
||||
$this->emulators[] = new ReverseEmulator($emulator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function tokenize(string $code, ?ErrorHandler $errorHandler = null): array {
|
||||
$emulators = array_filter($this->emulators, function ($emulator) use ($code) {
|
||||
return $emulator->isEmulationNeeded($code);
|
||||
});
|
||||
|
||||
if (empty($emulators)) {
|
||||
// Nothing to emulate, yay
|
||||
return parent::tokenize($code, $errorHandler);
|
||||
}
|
||||
|
||||
if ($errorHandler === null) {
|
||||
$errorHandler = new ErrorHandler\Throwing();
|
||||
}
|
||||
|
||||
$this->patches = [];
|
||||
foreach ($emulators as $emulator) {
|
||||
$code = $emulator->preprocessCode($code, $this->patches);
|
||||
}
|
||||
|
||||
$collector = new ErrorHandler\Collecting();
|
||||
$tokens = parent::tokenize($code, $collector);
|
||||
$this->sortPatches();
|
||||
$tokens = $this->fixupTokens($tokens);
|
||||
|
||||
$errors = $collector->getErrors();
|
||||
if (!empty($errors)) {
|
||||
$this->fixupErrors($errors);
|
||||
foreach ($errors as $error) {
|
||||
$errorHandler->handleError($error);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($emulators as $emulator) {
|
||||
$tokens = $emulator->emulate($code, $tokens);
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
private function isForwardEmulationNeeded(PhpVersion $emulatorPhpVersion): bool {
|
||||
return $this->hostPhpVersion->older($emulatorPhpVersion)
|
||||
&& $this->targetPhpVersion->newerOrEqual($emulatorPhpVersion);
|
||||
}
|
||||
|
||||
private function isReverseEmulationNeeded(PhpVersion $emulatorPhpVersion): bool {
|
||||
return $this->hostPhpVersion->newerOrEqual($emulatorPhpVersion)
|
||||
&& $this->targetPhpVersion->older($emulatorPhpVersion);
|
||||
}
|
||||
|
||||
private function sortPatches(): void {
|
||||
// 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];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<Token> $tokens
|
||||
* @return list<Token>
|
||||
*/
|
||||
private function fixupTokens(array $tokens): array {
|
||||
if (\count($this->patches) === 0) {
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
// 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
|
||||
$posDelta = 0;
|
||||
$lineDelta = 0;
|
||||
for ($i = 0, $c = \count($tokens); $i < $c; $i++) {
|
||||
$token = $tokens[$i];
|
||||
$pos = $token->pos;
|
||||
$token->pos += $posDelta;
|
||||
$token->line += $lineDelta;
|
||||
$localPosDelta = 0;
|
||||
$len = \strlen($token->text);
|
||||
while ($patchPos >= $pos && $patchPos < $pos + $len) {
|
||||
$patchTextLen = \strlen($patchText);
|
||||
if ($patchType === 'remove') {
|
||||
if ($patchPos === $pos && $patchTextLen === $len) {
|
||||
// Remove token entirely
|
||||
array_splice($tokens, $i, 1, []);
|
||||
$i--;
|
||||
$c--;
|
||||
} else {
|
||||
// Remove from token string
|
||||
$token->text = substr_replace(
|
||||
$token->text, '', $patchPos - $pos + $localPosDelta, $patchTextLen
|
||||
);
|
||||
$localPosDelta -= $patchTextLen;
|
||||
}
|
||||
$lineDelta -= \substr_count($patchText, "\n");
|
||||
} elseif ($patchType === 'add') {
|
||||
// Insert into the token string
|
||||
$token->text = substr_replace(
|
||||
$token->text, $patchText, $patchPos - $pos + $localPosDelta, 0
|
||||
);
|
||||
$localPosDelta += $patchTextLen;
|
||||
$lineDelta += \substr_count($patchText, "\n");
|
||||
} elseif ($patchType === 'replace') {
|
||||
// Replace inside the token string
|
||||
$token->text = substr_replace(
|
||||
$token->text, $patchText, $patchPos - $pos + $localPosDelta, $patchTextLen
|
||||
);
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
// Fetch the next patch
|
||||
$patchIdx++;
|
||||
if ($patchIdx >= \count($this->patches)) {
|
||||
// No more patches. However, we still need to adjust position.
|
||||
$patchPos = \PHP_INT_MAX;
|
||||
break;
|
||||
}
|
||||
|
||||
list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
|
||||
}
|
||||
|
||||
$posDelta += $localPosDelta;
|
||||
}
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixup line and position information in errors.
|
||||
*
|
||||
* @param Error[] $errors
|
||||
*/
|
||||
private function fixupErrors(array $errors): void {
|
||||
foreach ($errors as $error) {
|
||||
$attrs = $error->getAttributes();
|
||||
|
||||
$posDelta = 0;
|
||||
$lineDelta = 0;
|
||||
foreach ($this->patches as $patch) {
|
||||
list($patchPos, $patchType, $patchText) = $patch;
|
||||
if ($patchPos >= $attrs['startFilePos']) {
|
||||
// No longer relevant
|
||||
break;
|
||||
}
|
||||
|
||||
if ($patchType === 'add') {
|
||||
$posDelta += strlen($patchText);
|
||||
$lineDelta += substr_count($patchText, "\n");
|
||||
} elseif ($patchType === 'remove') {
|
||||
$posDelta -= strlen($patchText);
|
||||
$lineDelta -= substr_count($patchText, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
$attrs['startFilePos'] += $posDelta;
|
||||
$attrs['endFilePos'] += $posDelta;
|
||||
$attrs['startLine'] += $lineDelta;
|
||||
$attrs['endLine'] += $lineDelta;
|
||||
$error->setAttributes($attrs);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+60
@@ -0,0 +1,60 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\Token;
|
||||
|
||||
abstract class KeywordEmulator extends TokenEmulator {
|
||||
abstract public function getKeywordString(): string;
|
||||
abstract public function getKeywordToken(): int;
|
||||
|
||||
public function isEmulationNeeded(string $code): bool {
|
||||
return strpos(strtolower($code), $this->getKeywordString()) !== false;
|
||||
}
|
||||
|
||||
/** @param Token[] $tokens */
|
||||
protected function isKeywordContext(array $tokens, int $pos): bool {
|
||||
$prevToken = $this->getPreviousNonSpaceToken($tokens, $pos);
|
||||
if ($prevToken === null) {
|
||||
return false;
|
||||
}
|
||||
return $prevToken->id !== \T_OBJECT_OPERATOR
|
||||
&& $prevToken->id !== \T_NULLSAFE_OBJECT_OPERATOR;
|
||||
}
|
||||
|
||||
public function emulate(string $code, array $tokens): array {
|
||||
$keywordString = $this->getKeywordString();
|
||||
foreach ($tokens as $i => $token) {
|
||||
if ($token->id === T_STRING && strtolower($token->text) === $keywordString
|
||||
&& $this->isKeywordContext($tokens, $i)) {
|
||||
$token->id = $this->getKeywordToken();
|
||||
}
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/** @param Token[] $tokens */
|
||||
private function getPreviousNonSpaceToken(array $tokens, int $start): ?Token {
|
||||
for ($i = $start - 1; $i >= 0; --$i) {
|
||||
if ($tokens[$i]->id === T_WHITESPACE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $tokens[$i];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function reverseEmulate(string $code, array $tokens): array {
|
||||
$keywordToken = $this->getKeywordToken();
|
||||
foreach ($tokens as $token) {
|
||||
if ($token->id === $keywordToken) {
|
||||
$token->id = \T_STRING;
|
||||
}
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
/**
|
||||
* Modifiers used (as a bit mask) by various flags subnodes, for example on classes, functions,
|
||||
* properties and constants.
|
||||
*/
|
||||
final class Modifiers {
|
||||
public const PUBLIC = 1;
|
||||
public const PROTECTED = 2;
|
||||
public const PRIVATE = 4;
|
||||
public const STATIC = 8;
|
||||
public const ABSTRACT = 16;
|
||||
public const FINAL = 32;
|
||||
public const READONLY = 64;
|
||||
public const PUBLIC_SET = 128;
|
||||
public const PROTECTED_SET = 256;
|
||||
public const PRIVATE_SET = 512;
|
||||
|
||||
public const VISIBILITY_MASK = self::PUBLIC | self::PROTECTED | self::PRIVATE;
|
||||
|
||||
public const VISIBILITY_SET_MASK = self::PUBLIC_SET | self::PROTECTED_SET | self::PRIVATE_SET;
|
||||
|
||||
private const TO_STRING_MAP = [
|
||||
self::PUBLIC => 'public',
|
||||
self::PROTECTED => 'protected',
|
||||
self::PRIVATE => 'private',
|
||||
self::STATIC => 'static',
|
||||
self::ABSTRACT => 'abstract',
|
||||
self::FINAL => 'final',
|
||||
self::READONLY => 'readonly',
|
||||
self::PUBLIC_SET => 'public(set)',
|
||||
self::PROTECTED_SET => 'protected(set)',
|
||||
self::PRIVATE_SET => 'private(set)',
|
||||
];
|
||||
|
||||
public static function toString(int $modifier): string {
|
||||
if (!isset(self::TO_STRING_MAP[$modifier])) {
|
||||
throw new \InvalidArgumentException("Unknown modifier $modifier");
|
||||
}
|
||||
return self::TO_STRING_MAP[$modifier];
|
||||
}
|
||||
|
||||
private static function isValidModifier(int $modifier): bool {
|
||||
$isPow2 = ($modifier & ($modifier - 1)) == 0 && $modifier != 0;
|
||||
return $isPow2 && $modifier <= self::PRIVATE_SET;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function verifyClassModifier(int $a, int $b): void {
|
||||
assert(self::isValidModifier($b));
|
||||
if (($a & $b) != 0) {
|
||||
throw new Error(
|
||||
'Multiple ' . self::toString($b) . ' modifiers are not allowed');
|
||||
}
|
||||
|
||||
if ($a & 48 && $b & 48) {
|
||||
throw new Error('Cannot use the final modifier on an abstract class');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function verifyModifier(int $a, int $b): void {
|
||||
assert(self::isValidModifier($b));
|
||||
if (($a & Modifiers::VISIBILITY_MASK && $b & Modifiers::VISIBILITY_MASK) ||
|
||||
($a & Modifiers::VISIBILITY_SET_MASK && $b & Modifiers::VISIBILITY_SET_MASK)
|
||||
) {
|
||||
throw new Error('Multiple access type modifiers are not allowed');
|
||||
}
|
||||
|
||||
if (($a & $b) != 0) {
|
||||
throw new Error(
|
||||
'Multiple ' . self::toString($b) . ' modifiers are not allowed');
|
||||
}
|
||||
|
||||
if ($a & 48 && $b & 48) {
|
||||
throw new Error('Cannot use the final modifier on an abstract class member');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class NameContext {
|
||||
/** @var null|Name Current namespace */
|
||||
protected ?Name $namespace;
|
||||
|
||||
/** @var Name[][] Map of format [aliasType => [aliasName => originalName]] */
|
||||
protected array $aliases = [];
|
||||
|
||||
/** @var Name[][] Same as $aliases but preserving original case */
|
||||
protected array $origAliases = [];
|
||||
|
||||
/** @var ErrorHandler Error handler */
|
||||
protected ErrorHandler $errorHandler;
|
||||
|
||||
/**
|
||||
* Create a name context.
|
||||
*
|
||||
* @param ErrorHandler $errorHandler Error handling used to report errors
|
||||
*/
|
||||
public function __construct(ErrorHandler $errorHandler) {
|
||||
$this->errorHandler = $errorHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new namespace.
|
||||
*
|
||||
* This also resets the alias table.
|
||||
*
|
||||
* @param Name|null $namespace Null is the global namespace
|
||||
*/
|
||||
public function startNamespace(?Name $namespace = null): void {
|
||||
$this->namespace = $namespace;
|
||||
$this->origAliases = $this->aliases = [
|
||||
Stmt\Use_::TYPE_NORMAL => [],
|
||||
Stmt\Use_::TYPE_FUNCTION => [],
|
||||
Stmt\Use_::TYPE_CONSTANT => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an alias / import.
|
||||
*
|
||||
* @param Name $name Original name
|
||||
* @param string $aliasName Aliased name
|
||||
* @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_*
|
||||
* @param array<string, mixed> $errorAttrs Attributes to use to report an error
|
||||
*/
|
||||
public function addAlias(Name $name, string $aliasName, int $type, array $errorAttrs = []): void {
|
||||
// Constant names are case sensitive, everything else case insensitive
|
||||
if ($type === Stmt\Use_::TYPE_CONSTANT) {
|
||||
$aliasLookupName = $aliasName;
|
||||
} else {
|
||||
$aliasLookupName = strtolower($aliasName);
|
||||
}
|
||||
|
||||
if (isset($this->aliases[$type][$aliasLookupName])) {
|
||||
$typeStringMap = [
|
||||
Stmt\Use_::TYPE_NORMAL => '',
|
||||
Stmt\Use_::TYPE_FUNCTION => 'function ',
|
||||
Stmt\Use_::TYPE_CONSTANT => 'const ',
|
||||
];
|
||||
|
||||
$this->errorHandler->handleError(new Error(
|
||||
sprintf(
|
||||
'Cannot use %s%s as %s because the name is already in use',
|
||||
$typeStringMap[$type], $name, $aliasName
|
||||
),
|
||||
$errorAttrs
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
$this->aliases[$type][$aliasLookupName] = $name;
|
||||
$this->origAliases[$type][$aliasName] = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current namespace.
|
||||
*
|
||||
* @return null|Name Namespace (or null if global namespace)
|
||||
*/
|
||||
public function getNamespace(): ?Name {
|
||||
return $this->namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get resolved name.
|
||||
*
|
||||
* @param Name $name Name to resolve
|
||||
* @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_{FUNCTION|CONSTANT}
|
||||
*
|
||||
* @return null|Name Resolved name, or null if static resolution is not possible
|
||||
*/
|
||||
public function getResolvedName(Name $name, int $type): ?Name {
|
||||
// don't resolve special class names
|
||||
if ($type === Stmt\Use_::TYPE_NORMAL && $name->isSpecialClassName()) {
|
||||
if (!$name->isUnqualified()) {
|
||||
$this->errorHandler->handleError(new Error(
|
||||
sprintf("'\\%s' is an invalid class name", $name->toString()),
|
||||
$name->getAttributes()
|
||||
));
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
// fully qualified names are already resolved
|
||||
if ($name->isFullyQualified()) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
// Try to resolve aliases
|
||||
if (null !== $resolvedName = $this->resolveAlias($name, $type)) {
|
||||
return $resolvedName;
|
||||
}
|
||||
|
||||
if ($type !== Stmt\Use_::TYPE_NORMAL && $name->isUnqualified()) {
|
||||
if (null === $this->namespace) {
|
||||
// outside of a namespace unaliased unqualified is same as fully qualified
|
||||
return new FullyQualified($name, $name->getAttributes());
|
||||
}
|
||||
|
||||
// Cannot resolve statically
|
||||
return null;
|
||||
}
|
||||
|
||||
// if no alias exists prepend current namespace
|
||||
return FullyQualified::concat($this->namespace, $name, $name->getAttributes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get resolved class name.
|
||||
*
|
||||
* @param Name $name Class ame to resolve
|
||||
*
|
||||
* @return Name Resolved name
|
||||
*/
|
||||
public function getResolvedClassName(Name $name): Name {
|
||||
return $this->getResolvedName($name, Stmt\Use_::TYPE_NORMAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get possible ways of writing a fully qualified name (e.g., by making use of aliases).
|
||||
*
|
||||
* @param string $name Fully-qualified name (without leading namespace separator)
|
||||
* @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_*
|
||||
*
|
||||
* @return Name[] Possible representations of the name
|
||||
*/
|
||||
public function getPossibleNames(string $name, int $type): array {
|
||||
$lcName = strtolower($name);
|
||||
|
||||
if ($type === Stmt\Use_::TYPE_NORMAL) {
|
||||
// self, parent and static must always be unqualified
|
||||
if ($lcName === "self" || $lcName === "parent" || $lcName === "static") {
|
||||
return [new Name($name)];
|
||||
}
|
||||
}
|
||||
|
||||
// Collect possible ways to write this name, starting with the fully-qualified name
|
||||
$possibleNames = [new FullyQualified($name)];
|
||||
|
||||
if (null !== $nsRelativeName = $this->getNamespaceRelativeName($name, $lcName, $type)) {
|
||||
// Make sure there is no alias that makes the normally namespace-relative name
|
||||
// into something else
|
||||
if (null === $this->resolveAlias($nsRelativeName, $type)) {
|
||||
$possibleNames[] = $nsRelativeName;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for relevant namespace use statements
|
||||
foreach ($this->origAliases[Stmt\Use_::TYPE_NORMAL] as $alias => $orig) {
|
||||
$lcOrig = $orig->toLowerString();
|
||||
if (0 === strpos($lcName, $lcOrig . '\\')) {
|
||||
$possibleNames[] = new Name($alias . substr($name, strlen($lcOrig)));
|
||||
}
|
||||
}
|
||||
|
||||
// Check for relevant type-specific use statements
|
||||
foreach ($this->origAliases[$type] as $alias => $orig) {
|
||||
if ($type === Stmt\Use_::TYPE_CONSTANT) {
|
||||
// Constants are complicated-sensitive
|
||||
$normalizedOrig = $this->normalizeConstName($orig->toString());
|
||||
if ($normalizedOrig === $this->normalizeConstName($name)) {
|
||||
$possibleNames[] = new Name($alias);
|
||||
}
|
||||
} else {
|
||||
// Everything else is case-insensitive
|
||||
if ($orig->toLowerString() === $lcName) {
|
||||
$possibleNames[] = new Name($alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $possibleNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shortest representation of this fully-qualified name.
|
||||
*
|
||||
* @param string $name Fully-qualified name (without leading namespace separator)
|
||||
* @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_*
|
||||
*
|
||||
* @return Name Shortest representation
|
||||
*/
|
||||
public function getShortName(string $name, int $type): Name {
|
||||
$possibleNames = $this->getPossibleNames($name, $type);
|
||||
|
||||
// Find shortest name
|
||||
$shortestName = null;
|
||||
$shortestLength = \INF;
|
||||
foreach ($possibleNames as $possibleName) {
|
||||
$length = strlen($possibleName->toCodeString());
|
||||
if ($length < $shortestLength) {
|
||||
$shortestName = $possibleName;
|
||||
$shortestLength = $length;
|
||||
}
|
||||
}
|
||||
|
||||
return $shortestName;
|
||||
}
|
||||
|
||||
private function resolveAlias(Name $name, int $type): ?FullyQualified {
|
||||
$firstPart = $name->getFirst();
|
||||
|
||||
if ($name->isQualified()) {
|
||||
// resolve aliases for qualified names, always against class alias table
|
||||
$checkName = strtolower($firstPart);
|
||||
if (isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$checkName])) {
|
||||
$alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$checkName];
|
||||
return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes());
|
||||
}
|
||||
} elseif ($name->isUnqualified()) {
|
||||
// constant aliases are case-sensitive, function aliases case-insensitive
|
||||
$checkName = $type === Stmt\Use_::TYPE_CONSTANT ? $firstPart : strtolower($firstPart);
|
||||
if (isset($this->aliases[$type][$checkName])) {
|
||||
// resolve unqualified aliases
|
||||
return new FullyQualified($this->aliases[$type][$checkName], $name->getAttributes());
|
||||
}
|
||||
}
|
||||
|
||||
// No applicable aliases
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getNamespaceRelativeName(string $name, string $lcName, int $type): ?Name {
|
||||
if (null === $this->namespace) {
|
||||
return new Name($name);
|
||||
}
|
||||
|
||||
if ($type === Stmt\Use_::TYPE_CONSTANT) {
|
||||
// The constants true/false/null always resolve to the global symbols, even inside a
|
||||
// namespace, so they may be used without qualification
|
||||
if ($lcName === "true" || $lcName === "false" || $lcName === "null") {
|
||||
return new Name($name);
|
||||
}
|
||||
}
|
||||
|
||||
$namespacePrefix = strtolower($this->namespace . '\\');
|
||||
if (0 === strpos($lcName, $namespacePrefix)) {
|
||||
return new Name(substr($name, strlen($namespacePrefix)));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function normalizeConstName(string $name): string {
|
||||
$nsSep = strrpos($name, '\\');
|
||||
if (false === $nsSep) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
// Constants have case-insensitive namespace and case-sensitive short-name
|
||||
$ns = substr($name, 0, $nsSep);
|
||||
$shortName = substr($name, $nsSep + 1);
|
||||
return strtolower($ns) . '\\' . $shortName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
interface Node {
|
||||
/**
|
||||
* Gets the type of the node.
|
||||
*
|
||||
* @psalm-return non-empty-string
|
||||
* @return string Type of the node
|
||||
*/
|
||||
public function getType(): string;
|
||||
|
||||
/**
|
||||
* Gets the names of the sub nodes.
|
||||
*
|
||||
* @return string[] Names of sub nodes
|
||||
*/
|
||||
public function getSubNodeNames(): array;
|
||||
|
||||
/**
|
||||
* Gets line the node started in (alias of getStartLine).
|
||||
*
|
||||
* @return int Start line (or -1 if not available)
|
||||
* @phpstan-return -1|positive-int
|
||||
*
|
||||
* @deprecated Use getStartLine() instead
|
||||
*/
|
||||
public function getLine(): int;
|
||||
|
||||
/**
|
||||
* Gets line the node started in.
|
||||
*
|
||||
* Requires the 'startLine' attribute to be enabled in the lexer (enabled by default).
|
||||
*
|
||||
* @return int Start line (or -1 if not available)
|
||||
* @phpstan-return -1|positive-int
|
||||
*/
|
||||
public function getStartLine(): int;
|
||||
|
||||
/**
|
||||
* Gets the line the node ended in.
|
||||
*
|
||||
* Requires the 'endLine' attribute to be enabled in the lexer (enabled by default).
|
||||
*
|
||||
* @return int End line (or -1 if not available)
|
||||
* @phpstan-return -1|positive-int
|
||||
*/
|
||||
public function getEndLine(): int;
|
||||
|
||||
/**
|
||||
* Gets the token offset of the first token that is part of this node.
|
||||
*
|
||||
* The offset is an index into the array returned by Lexer::getTokens().
|
||||
*
|
||||
* Requires the 'startTokenPos' attribute to be enabled in the lexer (DISABLED by default).
|
||||
*
|
||||
* @return int Token start position (or -1 if not available)
|
||||
*/
|
||||
public function getStartTokenPos(): int;
|
||||
|
||||
/**
|
||||
* Gets the token offset of the last token that is part of this node.
|
||||
*
|
||||
* The offset is an index into the array returned by Lexer::getTokens().
|
||||
*
|
||||
* Requires the 'endTokenPos' attribute to be enabled in the lexer (DISABLED by default).
|
||||
*
|
||||
* @return int Token end position (or -1 if not available)
|
||||
*/
|
||||
public function getEndTokenPos(): int;
|
||||
|
||||
/**
|
||||
* Gets the file offset of the first character that is part of this node.
|
||||
*
|
||||
* Requires the 'startFilePos' attribute to be enabled in the lexer (DISABLED by default).
|
||||
*
|
||||
* @return int File start position (or -1 if not available)
|
||||
*/
|
||||
public function getStartFilePos(): int;
|
||||
|
||||
/**
|
||||
* Gets the file offset of the last character that is part of this node.
|
||||
*
|
||||
* Requires the 'endFilePos' attribute to be enabled in the lexer (DISABLED by default).
|
||||
*
|
||||
* @return int File end position (or -1 if not available)
|
||||
*/
|
||||
public function getEndFilePos(): int;
|
||||
|
||||
/**
|
||||
* Gets all comments directly preceding this node.
|
||||
*
|
||||
* The comments are also available through the "comments" attribute.
|
||||
*
|
||||
* @return Comment[]
|
||||
*/
|
||||
public function getComments(): array;
|
||||
|
||||
/**
|
||||
* Gets the doc comment of the node.
|
||||
*
|
||||
* @return null|Comment\Doc Doc comment object or null
|
||||
*/
|
||||
public function getDocComment(): ?Comment\Doc;
|
||||
|
||||
/**
|
||||
* Sets the doc comment of the node.
|
||||
*
|
||||
* This will either replace an existing doc comment or add it to the comments array.
|
||||
*
|
||||
* @param Comment\Doc $docComment Doc comment to set
|
||||
*/
|
||||
public function setDocComment(Comment\Doc $docComment): void;
|
||||
|
||||
/**
|
||||
* Sets an attribute on a node.
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function setAttribute(string $key, $value): void;
|
||||
|
||||
/**
|
||||
* Returns whether an attribute exists.
|
||||
*/
|
||||
public function hasAttribute(string $key): bool;
|
||||
|
||||
/**
|
||||
* Returns the value of an attribute.
|
||||
*
|
||||
* @param mixed $default
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAttribute(string $key, $default = null);
|
||||
|
||||
/**
|
||||
* Returns all the attributes of this node.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getAttributes(): array;
|
||||
|
||||
/**
|
||||
* Replaces all the attributes of this node.
|
||||
*
|
||||
* @param array<string, mixed> $attributes
|
||||
*/
|
||||
public function setAttributes(array $attributes): void;
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
require __DIR__ . '/../ArrayItem.php';
|
||||
|
||||
if (false) {
|
||||
// For classmap-authoritative support.
|
||||
class ArrayItem extends \PhpParser\Node\ArrayItem {
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
require __DIR__ . '/../ClosureUse.php';
|
||||
|
||||
if (false) {
|
||||
// For classmap-authoritative support.
|
||||
class ClosureUse extends \PhpParser\Node\ClosureUse {
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
/**
|
||||
* Represents a non-namespaced name. Namespaced names are represented using Name nodes.
|
||||
*/
|
||||
class Identifier extends NodeAbstract {
|
||||
/**
|
||||
* @psalm-var non-empty-string
|
||||
* @var string Identifier as string
|
||||
*/
|
||||
public string $name;
|
||||
|
||||
/** @var array<string, bool> */
|
||||
private static array $specialClassNames = [
|
||||
'self' => true,
|
||||
'parent' => true,
|
||||
'static' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructs an identifier node.
|
||||
*
|
||||
* @param string $name Identifier as string
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(string $name, array $attributes = []) {
|
||||
if ($name === '') {
|
||||
throw new \InvalidArgumentException('Identifier name cannot be empty');
|
||||
}
|
||||
|
||||
$this->attributes = $attributes;
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function getSubNodeNames(): array {
|
||||
return ['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get identifier as string.
|
||||
*
|
||||
* @psalm-return non-empty-string
|
||||
* @return string Identifier as string.
|
||||
*/
|
||||
public function toString(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get lowercased identifier as string.
|
||||
*
|
||||
* @psalm-return non-empty-string&lowercase-string
|
||||
* @return string Lowercased identifier as string
|
||||
*/
|
||||
public function toLowerString(): string {
|
||||
return strtolower($this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the identifier is a special class name (self, parent or static).
|
||||
*
|
||||
* @return bool Whether identifier is a special class name
|
||||
*/
|
||||
public function isSpecialClassName(): bool {
|
||||
return isset(self::$specialClassNames[strtolower($this->name)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get identifier as string.
|
||||
*
|
||||
* @psalm-return non-empty-string
|
||||
* @return string Identifier as string
|
||||
*/
|
||||
public function __toString(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getType(): string {
|
||||
return 'Identifier';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
class Name extends NodeAbstract {
|
||||
/**
|
||||
* @psalm-var non-empty-string
|
||||
* @var string Name as string
|
||||
*/
|
||||
public string $name;
|
||||
|
||||
/** @var array<string, bool> */
|
||||
private static array $specialClassNames = [
|
||||
'self' => true,
|
||||
'parent' => true,
|
||||
'static' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructs a name node.
|
||||
*
|
||||
* @param string|string[]|self $name Name as string, part array or Name instance (copy ctor)
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
*/
|
||||
final public function __construct($name, array $attributes = []) {
|
||||
$this->attributes = $attributes;
|
||||
$this->name = self::prepareName($name);
|
||||
}
|
||||
|
||||
public function getSubNodeNames(): array {
|
||||
return ['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parts of name (split by the namespace separator).
|
||||
*
|
||||
* @psalm-return non-empty-list<string>
|
||||
* @return string[] Parts of name
|
||||
*/
|
||||
public function getParts(): array {
|
||||
return \explode('\\', $this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first part of the name, i.e. everything before the first namespace separator.
|
||||
*
|
||||
* @return string First part of the name
|
||||
*/
|
||||
public function getFirst(): string {
|
||||
if (false !== $pos = \strpos($this->name, '\\')) {
|
||||
return \substr($this->name, 0, $pos);
|
||||
}
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last part of the name, i.e. everything after the last namespace separator.
|
||||
*
|
||||
* @return string Last part of the name
|
||||
*/
|
||||
public function getLast(): string {
|
||||
if (false !== $pos = \strrpos($this->name, '\\')) {
|
||||
return \substr($this->name, $pos + 1);
|
||||
}
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the name is unqualified. (E.g. Name)
|
||||
*
|
||||
* @return bool Whether the name is unqualified
|
||||
*/
|
||||
public function isUnqualified(): bool {
|
||||
return false === \strpos($this->name, '\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the name is qualified. (E.g. Name\Name)
|
||||
*
|
||||
* @return bool Whether the name is qualified
|
||||
*/
|
||||
public function isQualified(): bool {
|
||||
return false !== \strpos($this->name, '\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the name is fully qualified. (E.g. \Name)
|
||||
*
|
||||
* @return bool Whether the name is fully qualified
|
||||
*/
|
||||
public function isFullyQualified(): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the name is explicitly relative to the current namespace. (E.g. namespace\Name)
|
||||
*
|
||||
* @return bool Whether the name is relative
|
||||
*/
|
||||
public function isRelative(): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*
|
||||
* @psalm-return non-empty-string
|
||||
* @return string String representation
|
||||
*/
|
||||
public function toString(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the name as it would occur in code (e.g., including
|
||||
* leading backslash for fully qualified names.
|
||||
*
|
||||
* @psalm-return non-empty-string
|
||||
* @return string String representation
|
||||
*/
|
||||
public function toCodeString(): string {
|
||||
return $this->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns lowercased string representation of the name, without taking the name type into
|
||||
* account (e.g., no leading backslash for fully qualified names).
|
||||
*
|
||||
* @psalm-return non-empty-string&lowercase-string
|
||||
* @return string Lowercased string representation
|
||||
*/
|
||||
public function toLowerString(): string {
|
||||
return strtolower($this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the identifier is a special class name (self, parent or static).
|
||||
*
|
||||
* @return bool Whether identifier is a special class name
|
||||
*/
|
||||
public function isSpecialClassName(): bool {
|
||||
return isset(self::$specialClassNames[strtolower($this->name)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the name by imploding the namespace parts with the
|
||||
* namespace separator.
|
||||
*
|
||||
* @psalm-return non-empty-string
|
||||
* @return string String representation
|
||||
*/
|
||||
public function __toString(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a slice of a name (similar to array_slice).
|
||||
*
|
||||
* This method returns a new instance of the same type as the original and with the same
|
||||
* attributes.
|
||||
*
|
||||
* If the slice is empty, null is returned. The null value will be correctly handled in
|
||||
* concatenations using concat().
|
||||
*
|
||||
* Offset and length have the same meaning as in array_slice().
|
||||
*
|
||||
* @param int $offset Offset to start the slice at (may be negative)
|
||||
* @param int|null $length Length of the slice (may be negative)
|
||||
*
|
||||
* @return static|null Sliced name
|
||||
*/
|
||||
public function slice(int $offset, ?int $length = null) {
|
||||
if ($offset === 1 && $length === null) {
|
||||
// Short-circuit the common case.
|
||||
if (false !== $pos = \strpos($this->name, '\\')) {
|
||||
return new static(\substr($this->name, $pos + 1));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
$parts = \explode('\\', $this->name);
|
||||
$numParts = \count($parts);
|
||||
|
||||
$realOffset = $offset < 0 ? $offset + $numParts : $offset;
|
||||
if ($realOffset < 0 || $realOffset > $numParts) {
|
||||
throw new \OutOfBoundsException(sprintf('Offset %d is out of bounds', $offset));
|
||||
}
|
||||
|
||||
if (null === $length) {
|
||||
$realLength = $numParts - $realOffset;
|
||||
} else {
|
||||
$realLength = $length < 0 ? $length + $numParts - $realOffset : $length;
|
||||
if ($realLength < 0 || $realLength > $numParts - $realOffset) {
|
||||
throw new \OutOfBoundsException(sprintf('Length %d is out of bounds', $length));
|
||||
}
|
||||
}
|
||||
|
||||
if ($realLength === 0) {
|
||||
// Empty slice is represented as null
|
||||
return null;
|
||||
}
|
||||
|
||||
return new static(array_slice($parts, $realOffset, $realLength), $this->attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenate two names, yielding a new Name instance.
|
||||
*
|
||||
* The type of the generated instance depends on which class this method is called on, for
|
||||
* example Name\FullyQualified::concat() will yield a Name\FullyQualified instance.
|
||||
*
|
||||
* If one of the arguments is null, a new instance of the other name will be returned. If both
|
||||
* arguments are null, null will be returned. As such, writing
|
||||
* Name::concat($namespace, $shortName)
|
||||
* where $namespace is a Name node or null will work as expected.
|
||||
*
|
||||
* @param string|string[]|self|null $name1 The first name
|
||||
* @param string|string[]|self|null $name2 The second name
|
||||
* @param array<string, mixed> $attributes Attributes to assign to concatenated name
|
||||
*
|
||||
* @return static|null Concatenated name
|
||||
*/
|
||||
public static function concat($name1, $name2, array $attributes = []) {
|
||||
if (null === $name1 && null === $name2) {
|
||||
return null;
|
||||
}
|
||||
if (null === $name1) {
|
||||
return new static($name2, $attributes);
|
||||
}
|
||||
if (null === $name2) {
|
||||
return new static($name1, $attributes);
|
||||
} else {
|
||||
return new static(
|
||||
self::prepareName($name1) . '\\' . self::prepareName($name2), $attributes
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a (string, array or Name node) name for use in name changing methods by converting
|
||||
* it to a string.
|
||||
*
|
||||
* @param string|string[]|self $name Name to prepare
|
||||
*
|
||||
* @psalm-return non-empty-string
|
||||
* @return string Prepared name
|
||||
*/
|
||||
private static function prepareName($name): string {
|
||||
if (\is_string($name)) {
|
||||
if ('' === $name) {
|
||||
throw new \InvalidArgumentException('Name cannot be empty');
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
if (\is_array($name)) {
|
||||
if (empty($name)) {
|
||||
throw new \InvalidArgumentException('Name cannot be empty');
|
||||
}
|
||||
|
||||
return implode('\\', $name);
|
||||
}
|
||||
if ($name instanceof self) {
|
||||
return $name->name;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(
|
||||
'Expected string, array of parts or Name instance'
|
||||
);
|
||||
}
|
||||
|
||||
public function getType(): string {
|
||||
return 'Name';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\Modifiers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
class Param extends NodeAbstract {
|
||||
/** @var null|Identifier|Name|ComplexType Type declaration */
|
||||
public ?Node $type;
|
||||
/** @var bool Whether parameter is passed by reference */
|
||||
public bool $byRef;
|
||||
/** @var bool Whether this is a variadic argument */
|
||||
public bool $variadic;
|
||||
/** @var Expr\Variable|Expr\Error Parameter variable */
|
||||
public Expr $var;
|
||||
/** @var null|Expr Default value */
|
||||
public ?Expr $default;
|
||||
/** @var int Optional visibility flags */
|
||||
public int $flags;
|
||||
/** @var AttributeGroup[] PHP attribute groups */
|
||||
public array $attrGroups;
|
||||
/** @var PropertyHook[] Property hooks for promoted properties */
|
||||
public array $hooks;
|
||||
|
||||
/**
|
||||
* Constructs a parameter node.
|
||||
*
|
||||
* @param Expr\Variable|Expr\Error $var Parameter variable
|
||||
* @param null|Expr $default Default value
|
||||
* @param null|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<string, mixed> $attributes Additional attributes
|
||||
* @param int $flags Optional visibility flags
|
||||
* @param list<AttributeGroup> $attrGroups PHP attribute groups
|
||||
* @param PropertyHook[] $hooks Property hooks for promoted properties
|
||||
*/
|
||||
public function __construct(
|
||||
Expr $var, ?Expr $default = null, ?Node $type = null,
|
||||
bool $byRef = false, bool $variadic = false,
|
||||
array $attributes = [],
|
||||
int $flags = 0,
|
||||
array $attrGroups = [],
|
||||
array $hooks = []
|
||||
) {
|
||||
$this->attributes = $attributes;
|
||||
$this->type = $type;
|
||||
$this->byRef = $byRef;
|
||||
$this->variadic = $variadic;
|
||||
$this->var = $var;
|
||||
$this->default = $default;
|
||||
$this->flags = $flags;
|
||||
$this->attrGroups = $attrGroups;
|
||||
$this->hooks = $hooks;
|
||||
}
|
||||
|
||||
public function getSubNodeNames(): array {
|
||||
return ['attrGroups', 'flags', 'type', 'byRef', 'variadic', 'var', 'default', 'hooks'];
|
||||
}
|
||||
|
||||
public function getType(): string {
|
||||
return 'Param';
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this parameter uses constructor property promotion.
|
||||
*/
|
||||
public function isPromoted(): bool {
|
||||
return $this->flags !== 0 || $this->hooks !== [];
|
||||
}
|
||||
|
||||
public function isPublic(): bool {
|
||||
$public = (bool) ($this->flags & Modifiers::PUBLIC);
|
||||
if ($public) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->hooks === []) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ($this->flags & Modifiers::VISIBILITY_MASK) === 0;
|
||||
}
|
||||
|
||||
public function isProtected(): bool {
|
||||
return (bool) ($this->flags & Modifiers::PROTECTED);
|
||||
}
|
||||
|
||||
public function isPrivate(): bool {
|
||||
return (bool) ($this->flags & Modifiers::PRIVATE);
|
||||
}
|
||||
|
||||
public function isReadonly(): bool {
|
||||
return (bool) ($this->flags & Modifiers::READONLY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the promoted property has explicit public(set) visibility.
|
||||
*/
|
||||
public function isPublicSet(): bool {
|
||||
return (bool) ($this->flags & Modifiers::PUBLIC_SET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the promoted property has explicit protected(set) visibility.
|
||||
*/
|
||||
public function isProtectedSet(): bool {
|
||||
return (bool) ($this->flags & Modifiers::PROTECTED_SET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the promoted property has explicit private(set) visibility.
|
||||
*/
|
||||
public function isPrivateSet(): bool {
|
||||
return (bool) ($this->flags & Modifiers::PRIVATE_SET);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Scalar;
|
||||
|
||||
require __DIR__ . '/Float_.php';
|
||||
|
||||
if (false) {
|
||||
// For classmap-authoritative support.
|
||||
class DNumber extends Float_ {
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Scalar;
|
||||
|
||||
require __DIR__ . '/InterpolatedString.php';
|
||||
|
||||
if (false) {
|
||||
// For classmap-authoritative support.
|
||||
class Encapsed extends InterpolatedString {
|
||||
}
|
||||
}
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Scalar;
|
||||
|
||||
use PhpParser\Node\InterpolatedStringPart;
|
||||
|
||||
require __DIR__ . '/../InterpolatedStringPart.php';
|
||||
|
||||
if (false) {
|
||||
// For classmap-authoritative support.
|
||||
class EncapsedStringPart extends InterpolatedStringPart {
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Scalar;
|
||||
|
||||
require __DIR__ . '/Int_.php';
|
||||
|
||||
if (false) {
|
||||
// For classmap-authoritative support.
|
||||
class LNumber extends Int_ {
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Stmt;
|
||||
|
||||
use PhpParser\Node\DeclareItem;
|
||||
|
||||
require __DIR__ . '/../DeclareItem.php';
|
||||
|
||||
if (false) {
|
||||
// For classmap-authoritative support.
|
||||
class DeclareDeclare extends DeclareItem {
|
||||
}
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Stmt;
|
||||
|
||||
use PhpParser\Modifiers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\ComplexType;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\PropertyItem;
|
||||
|
||||
class Property extends Node\Stmt {
|
||||
/** @var int Modifiers */
|
||||
public int $flags;
|
||||
/** @var PropertyItem[] Properties */
|
||||
public array $props;
|
||||
/** @var null|Identifier|Name|ComplexType Type declaration */
|
||||
public ?Node $type;
|
||||
/** @var Node\AttributeGroup[] PHP attribute groups */
|
||||
public array $attrGroups;
|
||||
/** @var Node\PropertyHook[] Property hooks */
|
||||
public array $hooks;
|
||||
|
||||
/**
|
||||
* Constructs a class property list node.
|
||||
*
|
||||
* @param int $flags Modifiers
|
||||
* @param PropertyItem[] $props Properties
|
||||
* @param array<string, mixed> $attributes Additional attributes
|
||||
* @param null|Identifier|Name|ComplexType $type Type declaration
|
||||
* @param Node\AttributeGroup[] $attrGroups PHP attribute groups
|
||||
* @param Node\PropertyHook[] $hooks Property hooks
|
||||
*/
|
||||
public function __construct(int $flags, array $props, array $attributes = [], ?Node $type = null, array $attrGroups = [], array $hooks = []) {
|
||||
$this->attributes = $attributes;
|
||||
$this->flags = $flags;
|
||||
$this->props = $props;
|
||||
$this->type = $type;
|
||||
$this->attrGroups = $attrGroups;
|
||||
$this->hooks = $hooks;
|
||||
}
|
||||
|
||||
public function getSubNodeNames(): array {
|
||||
return ['attrGroups', 'flags', 'type', 'props', 'hooks'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the property is explicitly or implicitly public.
|
||||
*/
|
||||
public function isPublic(): bool {
|
||||
return ($this->flags & Modifiers::PUBLIC) !== 0
|
||||
|| ($this->flags & Modifiers::VISIBILITY_MASK) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the property is protected.
|
||||
*/
|
||||
public function isProtected(): bool {
|
||||
return (bool) ($this->flags & Modifiers::PROTECTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the property is private.
|
||||
*/
|
||||
public function isPrivate(): bool {
|
||||
return (bool) ($this->flags & Modifiers::PRIVATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the property is static.
|
||||
*/
|
||||
public function isStatic(): bool {
|
||||
return (bool) ($this->flags & Modifiers::STATIC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the property is readonly.
|
||||
*/
|
||||
public function isReadonly(): bool {
|
||||
return (bool) ($this->flags & Modifiers::READONLY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the property is abstract.
|
||||
*/
|
||||
public function isAbstract(): bool {
|
||||
return (bool) ($this->flags & Modifiers::ABSTRACT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the property is final.
|
||||
*/
|
||||
public function isFinal(): bool {
|
||||
return (bool) ($this->flags & Modifiers::FINAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the property has explicit public(set) visibility.
|
||||
*/
|
||||
public function isPublicSet(): bool {
|
||||
return (bool) ($this->flags & Modifiers::PUBLIC_SET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the property has explicit protected(set) visibility.
|
||||
*/
|
||||
public function isProtectedSet(): bool {
|
||||
return (bool) ($this->flags & Modifiers::PROTECTED_SET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the property has explicit private(set) visibility.
|
||||
*/
|
||||
public function isPrivateSet(): bool {
|
||||
return (bool) ($this->flags & Modifiers::PRIVATE_SET);
|
||||
}
|
||||
|
||||
public function getType(): string {
|
||||
return 'Stmt_Property';
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Stmt;
|
||||
|
||||
use PhpParser\Node\PropertyItem;
|
||||
|
||||
require __DIR__ . '/../PropertyItem.php';
|
||||
|
||||
if (false) {
|
||||
// For classmap-authoritative support.
|
||||
class PropertyProperty extends PropertyItem {
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Stmt;
|
||||
|
||||
require __DIR__ . '/../StaticVar.php';
|
||||
|
||||
if (false) {
|
||||
// For classmap-authoritative support.
|
||||
class StaticVar extends \PhpParser\Node\StaticVar {
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Stmt;
|
||||
|
||||
use PhpParser\Node\UseItem;
|
||||
|
||||
require __DIR__ . '/../UseItem.php';
|
||||
|
||||
if (false) {
|
||||
// For classmap-authoritative support.
|
||||
class UseUse extends UseItem {
|
||||
}
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
abstract class NodeAbstract implements Node, \JsonSerializable {
|
||||
/** @var array<string, mixed> Attributes */
|
||||
protected array $attributes;
|
||||
|
||||
/**
|
||||
* Creates a Node.
|
||||
*
|
||||
* @param array<string, mixed> $attributes Array of attributes
|
||||
*/
|
||||
public function __construct(array $attributes = []) {
|
||||
$this->attributes = $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets line the node started in (alias of getStartLine).
|
||||
*
|
||||
* @return int Start line (or -1 if not available)
|
||||
* @phpstan-return -1|positive-int
|
||||
*/
|
||||
public function getLine(): int {
|
||||
return $this->attributes['startLine'] ?? -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets line the node started in.
|
||||
*
|
||||
* Requires the 'startLine' attribute to be enabled in the lexer (enabled by default).
|
||||
*
|
||||
* @return int Start line (or -1 if not available)
|
||||
* @phpstan-return -1|positive-int
|
||||
*/
|
||||
public function getStartLine(): int {
|
||||
return $this->attributes['startLine'] ?? -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line the node ended in.
|
||||
*
|
||||
* Requires the 'endLine' attribute to be enabled in the lexer (enabled by default).
|
||||
*
|
||||
* @return int End line (or -1 if not available)
|
||||
* @phpstan-return -1|positive-int
|
||||
*/
|
||||
public function getEndLine(): int {
|
||||
return $this->attributes['endLine'] ?? -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the token offset of the first token that is part of this node.
|
||||
*
|
||||
* The offset is an index into the array returned by Lexer::getTokens().
|
||||
*
|
||||
* Requires the 'startTokenPos' attribute to be enabled in the lexer (DISABLED by default).
|
||||
*
|
||||
* @return int Token start position (or -1 if not available)
|
||||
*/
|
||||
public function getStartTokenPos(): int {
|
||||
return $this->attributes['startTokenPos'] ?? -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the token offset of the last token that is part of this node.
|
||||
*
|
||||
* The offset is an index into the array returned by Lexer::getTokens().
|
||||
*
|
||||
* Requires the 'endTokenPos' attribute to be enabled in the lexer (DISABLED by default).
|
||||
*
|
||||
* @return int Token end position (or -1 if not available)
|
||||
*/
|
||||
public function getEndTokenPos(): int {
|
||||
return $this->attributes['endTokenPos'] ?? -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file offset of the first character that is part of this node.
|
||||
*
|
||||
* Requires the 'startFilePos' attribute to be enabled in the lexer (DISABLED by default).
|
||||
*
|
||||
* @return int File start position (or -1 if not available)
|
||||
*/
|
||||
public function getStartFilePos(): int {
|
||||
return $this->attributes['startFilePos'] ?? -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file offset of the last character that is part of this node.
|
||||
*
|
||||
* Requires the 'endFilePos' attribute to be enabled in the lexer (DISABLED by default).
|
||||
*
|
||||
* @return int File end position (or -1 if not available)
|
||||
*/
|
||||
public function getEndFilePos(): int {
|
||||
return $this->attributes['endFilePos'] ?? -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all comments directly preceding this node.
|
||||
*
|
||||
* The comments are also available through the "comments" attribute.
|
||||
*
|
||||
* @return Comment[]
|
||||
*/
|
||||
public function getComments(): array {
|
||||
return $this->attributes['comments'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the doc comment of the node.
|
||||
*
|
||||
* @return null|Comment\Doc Doc comment object or null
|
||||
*/
|
||||
public function getDocComment(): ?Comment\Doc {
|
||||
$comments = $this->getComments();
|
||||
for ($i = count($comments) - 1; $i >= 0; $i--) {
|
||||
$comment = $comments[$i];
|
||||
if ($comment instanceof Comment\Doc) {
|
||||
return $comment;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the doc comment of the node.
|
||||
*
|
||||
* This will either replace an existing doc comment or add it to the comments array.
|
||||
*
|
||||
* @param Comment\Doc $docComment Doc comment to set
|
||||
*/
|
||||
public function setDocComment(Comment\Doc $docComment): void {
|
||||
$comments = $this->getComments();
|
||||
for ($i = count($comments) - 1; $i >= 0; $i--) {
|
||||
if ($comments[$i] instanceof Comment\Doc) {
|
||||
// Replace existing doc comment.
|
||||
$comments[$i] = $docComment;
|
||||
$this->setAttribute('comments', $comments);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Append new doc comment.
|
||||
$comments[] = $docComment;
|
||||
$this->setAttribute('comments', $comments);
|
||||
}
|
||||
|
||||
public function setAttribute(string $key, $value): void {
|
||||
$this->attributes[$key] = $value;
|
||||
}
|
||||
|
||||
public function hasAttribute(string $key): bool {
|
||||
return array_key_exists($key, $this->attributes);
|
||||
}
|
||||
|
||||
public function getAttribute(string $key, $default = null) {
|
||||
if (array_key_exists($key, $this->attributes)) {
|
||||
return $this->attributes[$key];
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
public function getAttributes(): array {
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
public function setAttributes(array $attributes): void {
|
||||
$this->attributes = $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function jsonSerialize(): array {
|
||||
return ['nodeType' => $this->getType()] + get_object_vars($this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\Include_;
|
||||
use PhpParser\Node\Expr\List_;
|
||||
use PhpParser\Node\Scalar\Int_;
|
||||
use PhpParser\Node\Scalar\InterpolatedString;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\Stmt\GroupUse;
|
||||
use PhpParser\Node\Stmt\Use_;
|
||||
use PhpParser\Node\UseItem;
|
||||
|
||||
class NodeDumper {
|
||||
private bool $dumpComments;
|
||||
private bool $dumpPositions;
|
||||
private bool $dumpOtherAttributes;
|
||||
private ?string $code;
|
||||
private string $res;
|
||||
private string $nl;
|
||||
|
||||
private const IGNORE_ATTRIBUTES = [
|
||||
'comments' => true,
|
||||
'startLine' => true,
|
||||
'endLine' => true,
|
||||
'startFilePos' => true,
|
||||
'endFilePos' => true,
|
||||
'startTokenPos' => true,
|
||||
'endTokenPos' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructs a NodeDumper.
|
||||
*
|
||||
* Supported options:
|
||||
* * bool dumpComments: Whether comments should be dumped.
|
||||
* * bool dumpPositions: Whether line/offset information should be dumped. To dump offset
|
||||
* information, the code needs to be passed to dump().
|
||||
* * bool dumpOtherAttributes: Whether non-comment, non-position attributes should be dumped.
|
||||
*
|
||||
* @param array $options Options (see description)
|
||||
*/
|
||||
public function __construct(array $options = []) {
|
||||
$this->dumpComments = !empty($options['dumpComments']);
|
||||
$this->dumpPositions = !empty($options['dumpPositions']);
|
||||
$this->dumpOtherAttributes = !empty($options['dumpOtherAttributes']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps a node or array.
|
||||
*
|
||||
* @param array|Node $node Node or array to dump
|
||||
* @param string|null $code Code corresponding to dumped AST. This only needs to be passed if
|
||||
* the dumpPositions option is enabled and the dumping of node offsets
|
||||
* is desired.
|
||||
*
|
||||
* @return string Dumped value
|
||||
*/
|
||||
public function dump($node, ?string $code = null): string {
|
||||
$this->code = $code;
|
||||
$this->res = '';
|
||||
$this->nl = "\n";
|
||||
$this->dumpRecursive($node, false);
|
||||
return $this->res;
|
||||
}
|
||||
|
||||
/** @param mixed $node */
|
||||
protected function dumpRecursive($node, bool $indent = true): void {
|
||||
if ($indent) {
|
||||
$this->nl .= " ";
|
||||
}
|
||||
if ($node instanceof Node) {
|
||||
$this->res .= $node->getType();
|
||||
if ($this->dumpPositions && null !== $p = $this->dumpPosition($node)) {
|
||||
$this->res .= $p;
|
||||
}
|
||||
$this->res .= '(';
|
||||
|
||||
foreach ($node->getSubNodeNames() as $key) {
|
||||
$this->res .= "$this->nl " . $key . ': ';
|
||||
|
||||
$value = $node->$key;
|
||||
if (\is_int($value)) {
|
||||
if ('flags' === $key || 'newModifier' === $key) {
|
||||
$this->res .= $this->dumpFlags($value);
|
||||
continue;
|
||||
}
|
||||
if ('type' === $key && $node instanceof Include_) {
|
||||
$this->res .= $this->dumpIncludeType($value);
|
||||
continue;
|
||||
}
|
||||
if ('type' === $key
|
||||
&& ($node instanceof Use_ || $node instanceof UseItem || $node instanceof GroupUse)) {
|
||||
$this->res .= $this->dumpUseType($value);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$this->dumpRecursive($value);
|
||||
}
|
||||
|
||||
if ($this->dumpComments && $comments = $node->getComments()) {
|
||||
$this->res .= "$this->nl comments: ";
|
||||
$this->dumpRecursive($comments);
|
||||
}
|
||||
|
||||
if ($this->dumpOtherAttributes) {
|
||||
foreach ($node->getAttributes() as $key => $value) {
|
||||
if (isset(self::IGNORE_ATTRIBUTES[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->res .= "$this->nl $key: ";
|
||||
if (\is_int($value)) {
|
||||
if ('kind' === $key) {
|
||||
if ($node instanceof Int_) {
|
||||
$this->res .= $this->dumpIntKind($value);
|
||||
continue;
|
||||
}
|
||||
if ($node instanceof String_ || $node instanceof InterpolatedString) {
|
||||
$this->res .= $this->dumpStringKind($value);
|
||||
continue;
|
||||
}
|
||||
if ($node instanceof Array_) {
|
||||
$this->res .= $this->dumpArrayKind($value);
|
||||
continue;
|
||||
}
|
||||
if ($node instanceof List_) {
|
||||
$this->res .= $this->dumpListKind($value);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->dumpRecursive($value);
|
||||
}
|
||||
}
|
||||
$this->res .= "$this->nl)";
|
||||
} elseif (\is_array($node)) {
|
||||
$this->res .= 'array(';
|
||||
foreach ($node as $key => $value) {
|
||||
$this->res .= "$this->nl " . $key . ': ';
|
||||
$this->dumpRecursive($value);
|
||||
}
|
||||
$this->res .= "$this->nl)";
|
||||
} elseif ($node instanceof Comment) {
|
||||
$this->res .= \str_replace("\n", $this->nl, $node->getReformattedText());
|
||||
} elseif (\is_string($node)) {
|
||||
$this->res .= \str_replace("\n", $this->nl, (string)$node);
|
||||
} elseif (\is_int($node) || \is_float($node)) {
|
||||
$this->res .= $node;
|
||||
} elseif (null === $node) {
|
||||
$this->res .= 'null';
|
||||
} elseif (false === $node) {
|
||||
$this->res .= 'false';
|
||||
} elseif (true === $node) {
|
||||
$this->res .= 'true';
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Can only dump nodes and arrays.');
|
||||
}
|
||||
if ($indent) {
|
||||
$this->nl = \substr($this->nl, 0, -4);
|
||||
}
|
||||
}
|
||||
|
||||
protected function dumpFlags(int $flags): string {
|
||||
$strs = [];
|
||||
if ($flags & Modifiers::PUBLIC) {
|
||||
$strs[] = 'PUBLIC';
|
||||
}
|
||||
if ($flags & Modifiers::PROTECTED) {
|
||||
$strs[] = 'PROTECTED';
|
||||
}
|
||||
if ($flags & Modifiers::PRIVATE) {
|
||||
$strs[] = 'PRIVATE';
|
||||
}
|
||||
if ($flags & Modifiers::ABSTRACT) {
|
||||
$strs[] = 'ABSTRACT';
|
||||
}
|
||||
if ($flags & Modifiers::STATIC) {
|
||||
$strs[] = 'STATIC';
|
||||
}
|
||||
if ($flags & Modifiers::FINAL) {
|
||||
$strs[] = 'FINAL';
|
||||
}
|
||||
if ($flags & Modifiers::READONLY) {
|
||||
$strs[] = 'READONLY';
|
||||
}
|
||||
if ($flags & Modifiers::PUBLIC_SET) {
|
||||
$strs[] = 'PUBLIC_SET';
|
||||
}
|
||||
if ($flags & Modifiers::PROTECTED_SET) {
|
||||
$strs[] = 'PROTECTED_SET';
|
||||
}
|
||||
if ($flags & Modifiers::PRIVATE_SET) {
|
||||
$strs[] = 'PRIVATE_SET';
|
||||
}
|
||||
|
||||
if ($strs) {
|
||||
return implode(' | ', $strs) . ' (' . $flags . ')';
|
||||
} else {
|
||||
return (string) $flags;
|
||||
}
|
||||
}
|
||||
|
||||
/** @param array<int, string> $map */
|
||||
private function dumpEnum(int $value, array $map): string {
|
||||
if (!isset($map[$value])) {
|
||||
return (string) $value;
|
||||
}
|
||||
return $map[$value] . ' (' . $value . ')';
|
||||
}
|
||||
|
||||
private function dumpIncludeType(int $type): string {
|
||||
return $this->dumpEnum($type, [
|
||||
Include_::TYPE_INCLUDE => 'TYPE_INCLUDE',
|
||||
Include_::TYPE_INCLUDE_ONCE => 'TYPE_INCLUDE_ONCE',
|
||||
Include_::TYPE_REQUIRE => 'TYPE_REQUIRE',
|
||||
Include_::TYPE_REQUIRE_ONCE => 'TYPE_REQUIRE_ONCE',
|
||||
]);
|
||||
}
|
||||
|
||||
private function dumpUseType(int $type): string {
|
||||
return $this->dumpEnum($type, [
|
||||
Use_::TYPE_UNKNOWN => 'TYPE_UNKNOWN',
|
||||
Use_::TYPE_NORMAL => 'TYPE_NORMAL',
|
||||
Use_::TYPE_FUNCTION => 'TYPE_FUNCTION',
|
||||
Use_::TYPE_CONSTANT => 'TYPE_CONSTANT',
|
||||
]);
|
||||
}
|
||||
|
||||
private function dumpIntKind(int $kind): string {
|
||||
return $this->dumpEnum($kind, [
|
||||
Int_::KIND_BIN => 'KIND_BIN',
|
||||
Int_::KIND_OCT => 'KIND_OCT',
|
||||
Int_::KIND_DEC => 'KIND_DEC',
|
||||
Int_::KIND_HEX => 'KIND_HEX',
|
||||
]);
|
||||
}
|
||||
|
||||
private function dumpStringKind(int $kind): string {
|
||||
return $this->dumpEnum($kind, [
|
||||
String_::KIND_SINGLE_QUOTED => 'KIND_SINGLE_QUOTED',
|
||||
String_::KIND_DOUBLE_QUOTED => 'KIND_DOUBLE_QUOTED',
|
||||
String_::KIND_HEREDOC => 'KIND_HEREDOC',
|
||||
String_::KIND_NOWDOC => 'KIND_NOWDOC',
|
||||
]);
|
||||
}
|
||||
|
||||
private function dumpArrayKind(int $kind): string {
|
||||
return $this->dumpEnum($kind, [
|
||||
Array_::KIND_LONG => 'KIND_LONG',
|
||||
Array_::KIND_SHORT => 'KIND_SHORT',
|
||||
]);
|
||||
}
|
||||
|
||||
private function dumpListKind(int $kind): string {
|
||||
return $this->dumpEnum($kind, [
|
||||
List_::KIND_LIST => 'KIND_LIST',
|
||||
List_::KIND_ARRAY => 'KIND_ARRAY',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump node position, if possible.
|
||||
*
|
||||
* @param Node $node Node for which to dump position
|
||||
*
|
||||
* @return string|null Dump of position, or null if position information not available
|
||||
*/
|
||||
protected function dumpPosition(Node $node): ?string {
|
||||
if (!$node->hasAttribute('startLine') || !$node->hasAttribute('endLine')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$start = $node->getStartLine();
|
||||
$end = $node->getEndLine();
|
||||
if ($node->hasAttribute('startFilePos') && $node->hasAttribute('endFilePos')
|
||||
&& null !== $this->code
|
||||
) {
|
||||
$start .= ':' . $this->toColumn($this->code, $node->getStartFilePos());
|
||||
$end .= ':' . $this->toColumn($this->code, $node->getEndFilePos());
|
||||
}
|
||||
return "[$start - $end]";
|
||||
}
|
||||
|
||||
// Copied from Error class
|
||||
private function toColumn(string $code, int $pos): int {
|
||||
if ($pos > strlen($code)) {
|
||||
throw new \RuntimeException('Invalid position information');
|
||||
}
|
||||
|
||||
$lineStartPos = strrpos($code, "\n", $pos - strlen($code));
|
||||
if (false === $lineStartPos) {
|
||||
$lineStartPos = -1;
|
||||
}
|
||||
|
||||
return $pos - $lineStartPos;
|
||||
}
|
||||
}
|
||||
+287
@@ -0,0 +1,287 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
class NodeTraverser implements NodeTraverserInterface {
|
||||
/**
|
||||
* @deprecated Use NodeVisitor::DONT_TRAVERSE_CHILDREN instead.
|
||||
*/
|
||||
public const DONT_TRAVERSE_CHILDREN = NodeVisitor::DONT_TRAVERSE_CHILDREN;
|
||||
|
||||
/**
|
||||
* @deprecated Use NodeVisitor::STOP_TRAVERSAL instead.
|
||||
*/
|
||||
public const STOP_TRAVERSAL = NodeVisitor::STOP_TRAVERSAL;
|
||||
|
||||
/**
|
||||
* @deprecated Use NodeVisitor::REMOVE_NODE instead.
|
||||
*/
|
||||
public const REMOVE_NODE = NodeVisitor::REMOVE_NODE;
|
||||
|
||||
/**
|
||||
* @deprecated Use NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN instead.
|
||||
*/
|
||||
public const DONT_TRAVERSE_CURRENT_AND_CHILDREN = NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
|
||||
|
||||
/** @var list<NodeVisitor> Visitors */
|
||||
protected array $visitors = [];
|
||||
|
||||
/** @var bool Whether traversal should be stopped */
|
||||
protected bool $stopTraversal;
|
||||
|
||||
/**
|
||||
* Create a traverser with the given visitors.
|
||||
*
|
||||
* @param NodeVisitor ...$visitors Node visitors
|
||||
*/
|
||||
public function __construct(NodeVisitor ...$visitors) {
|
||||
$this->visitors = $visitors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a visitor.
|
||||
*
|
||||
* @param NodeVisitor $visitor Visitor to add
|
||||
*/
|
||||
public function addVisitor(NodeVisitor $visitor): void {
|
||||
$this->visitors[] = $visitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an added visitor.
|
||||
*/
|
||||
public function removeVisitor(NodeVisitor $visitor): void {
|
||||
$index = array_search($visitor, $this->visitors);
|
||||
if ($index !== false) {
|
||||
array_splice($this->visitors, $index, 1, []);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses an array of nodes using the registered visitors.
|
||||
*
|
||||
* @param Node[] $nodes Array of nodes
|
||||
*
|
||||
* @return Node[] Traversed array of nodes
|
||||
*/
|
||||
public function traverse(array $nodes): array {
|
||||
$this->stopTraversal = false;
|
||||
|
||||
foreach ($this->visitors as $visitor) {
|
||||
if (null !== $return = $visitor->beforeTraverse($nodes)) {
|
||||
$nodes = $return;
|
||||
}
|
||||
}
|
||||
|
||||
$nodes = $this->traverseArray($nodes);
|
||||
|
||||
for ($i = \count($this->visitors) - 1; $i >= 0; --$i) {
|
||||
$visitor = $this->visitors[$i];
|
||||
if (null !== $return = $visitor->afterTraverse($nodes)) {
|
||||
$nodes = $return;
|
||||
}
|
||||
}
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively traverse a node.
|
||||
*
|
||||
* @param Node $node Node to traverse.
|
||||
*/
|
||||
protected function traverseNode(Node $node): void {
|
||||
foreach ($node->getSubNodeNames() as $name) {
|
||||
$subNode = $node->$name;
|
||||
|
||||
if (\is_array($subNode)) {
|
||||
$node->$name = $this->traverseArray($subNode);
|
||||
if ($this->stopTraversal) {
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$subNode instanceof Node) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$traverseChildren = true;
|
||||
$visitorIndex = -1;
|
||||
|
||||
foreach ($this->visitors as $visitorIndex => $visitor) {
|
||||
$return = $visitor->enterNode($subNode);
|
||||
if (null !== $return) {
|
||||
if ($return instanceof Node) {
|
||||
$this->ensureReplacementReasonable($subNode, $return);
|
||||
$subNode = $node->$name = $return;
|
||||
} elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) {
|
||||
$traverseChildren = false;
|
||||
} elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
|
||||
$traverseChildren = false;
|
||||
break;
|
||||
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
|
||||
$this->stopTraversal = true;
|
||||
break 2;
|
||||
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
|
||||
$node->$name = null;
|
||||
continue 2;
|
||||
} else {
|
||||
throw new \LogicException(
|
||||
'enterNode() returned invalid value of type ' . gettype($return)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($traverseChildren) {
|
||||
$this->traverseNode($subNode);
|
||||
if ($this->stopTraversal) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (; $visitorIndex >= 0; --$visitorIndex) {
|
||||
$visitor = $this->visitors[$visitorIndex];
|
||||
$return = $visitor->leaveNode($subNode);
|
||||
|
||||
if (null !== $return) {
|
||||
if ($return instanceof Node) {
|
||||
$this->ensureReplacementReasonable($subNode, $return);
|
||||
$subNode = $node->$name = $return;
|
||||
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
|
||||
$this->stopTraversal = true;
|
||||
break 2;
|
||||
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
|
||||
$node->$name = null;
|
||||
break;
|
||||
} elseif (\is_array($return)) {
|
||||
throw new \LogicException(
|
||||
'leaveNode() may only return an array ' .
|
||||
'if the parent structure is an array'
|
||||
);
|
||||
} else {
|
||||
throw new \LogicException(
|
||||
'leaveNode() returned invalid value of type ' . gettype($return)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively traverse array (usually of nodes).
|
||||
*
|
||||
* @param array $nodes Array to traverse
|
||||
*
|
||||
* @return array Result of traversal (may be original array or changed one)
|
||||
*/
|
||||
protected function traverseArray(array $nodes): array {
|
||||
$doNodes = [];
|
||||
|
||||
foreach ($nodes as $i => $node) {
|
||||
if (!$node instanceof Node) {
|
||||
if (\is_array($node)) {
|
||||
throw new \LogicException('Invalid node structure: Contains nested arrays');
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$traverseChildren = true;
|
||||
$visitorIndex = -1;
|
||||
|
||||
foreach ($this->visitors as $visitorIndex => $visitor) {
|
||||
$return = $visitor->enterNode($node);
|
||||
if (null !== $return) {
|
||||
if ($return instanceof Node) {
|
||||
$this->ensureReplacementReasonable($node, $return);
|
||||
$nodes[$i] = $node = $return;
|
||||
} elseif (\is_array($return)) {
|
||||
$doNodes[] = [$i, $return];
|
||||
continue 2;
|
||||
} elseif (NodeVisitor::REMOVE_NODE === $return) {
|
||||
$doNodes[] = [$i, []];
|
||||
continue 2;
|
||||
} elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) {
|
||||
$traverseChildren = false;
|
||||
} elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
|
||||
$traverseChildren = false;
|
||||
break;
|
||||
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
|
||||
$this->stopTraversal = true;
|
||||
break 2;
|
||||
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
|
||||
throw new \LogicException(
|
||||
'REPLACE_WITH_NULL can not be used if the parent structure is an array');
|
||||
} else {
|
||||
throw new \LogicException(
|
||||
'enterNode() returned invalid value of type ' . gettype($return)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($traverseChildren) {
|
||||
$this->traverseNode($node);
|
||||
if ($this->stopTraversal) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (; $visitorIndex >= 0; --$visitorIndex) {
|
||||
$visitor = $this->visitors[$visitorIndex];
|
||||
$return = $visitor->leaveNode($node);
|
||||
|
||||
if (null !== $return) {
|
||||
if ($return instanceof Node) {
|
||||
$this->ensureReplacementReasonable($node, $return);
|
||||
$nodes[$i] = $node = $return;
|
||||
} elseif (\is_array($return)) {
|
||||
$doNodes[] = [$i, $return];
|
||||
break;
|
||||
} elseif (NodeVisitor::REMOVE_NODE === $return) {
|
||||
$doNodes[] = [$i, []];
|
||||
break;
|
||||
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
|
||||
$this->stopTraversal = true;
|
||||
break 2;
|
||||
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
|
||||
throw new \LogicException(
|
||||
'REPLACE_WITH_NULL can not be used if the parent structure is an array');
|
||||
} else {
|
||||
throw new \LogicException(
|
||||
'leaveNode() returned invalid value of type ' . gettype($return)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($doNodes)) {
|
||||
while (list($i, $replace) = array_pop($doNodes)) {
|
||||
array_splice($nodes, $i, 1, $replace);
|
||||
}
|
||||
}
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
private function ensureReplacementReasonable(Node $old, Node $new): void {
|
||||
if ($old instanceof Node\Stmt && $new instanceof Node\Expr) {
|
||||
throw new \LogicException(
|
||||
"Trying to replace statement ({$old->getType()}) " .
|
||||
"with expression ({$new->getType()}). Are you missing a " .
|
||||
"Stmt_Expression wrapper?"
|
||||
);
|
||||
}
|
||||
|
||||
if ($old instanceof Node\Expr && $new instanceof Node\Stmt) {
|
||||
throw new \LogicException(
|
||||
"Trying to replace expression ({$old->getType()}) " .
|
||||
"with statement ({$new->getType()})"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\NodeVisitor;
|
||||
|
||||
use PhpParser\ErrorHandler;
|
||||
use PhpParser\NameContext;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
|
||||
class NameResolver extends NodeVisitorAbstract {
|
||||
/** @var NameContext Naming context */
|
||||
protected NameContext $nameContext;
|
||||
|
||||
/** @var bool Whether to preserve original names */
|
||||
protected bool $preserveOriginalNames;
|
||||
|
||||
/** @var bool Whether to replace resolved nodes in place, or to add resolvedNode attributes */
|
||||
protected bool $replaceNodes;
|
||||
|
||||
/**
|
||||
* Constructs a name resolution visitor.
|
||||
*
|
||||
* Options:
|
||||
* * preserveOriginalNames (default false): An "originalName" attribute will be added to
|
||||
* all name nodes that underwent resolution.
|
||||
* * replaceNodes (default true): Resolved names are replaced in-place. Otherwise, a
|
||||
* resolvedName attribute is added. (Names that cannot be statically resolved receive a
|
||||
* namespacedName attribute, as usual.)
|
||||
*
|
||||
* @param ErrorHandler|null $errorHandler Error handler
|
||||
* @param array{preserveOriginalNames?: bool, replaceNodes?: bool} $options Options
|
||||
*/
|
||||
public function __construct(?ErrorHandler $errorHandler = null, array $options = []) {
|
||||
$this->nameContext = new NameContext($errorHandler ?? new ErrorHandler\Throwing());
|
||||
$this->preserveOriginalNames = $options['preserveOriginalNames'] ?? false;
|
||||
$this->replaceNodes = $options['replaceNodes'] ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name resolution context.
|
||||
*/
|
||||
public function getNameContext(): NameContext {
|
||||
return $this->nameContext;
|
||||
}
|
||||
|
||||
public function beforeTraverse(array $nodes): ?array {
|
||||
$this->nameContext->startNamespace();
|
||||
return null;
|
||||
}
|
||||
|
||||
public function enterNode(Node $node) {
|
||||
if ($node instanceof Stmt\Namespace_) {
|
||||
$this->nameContext->startNamespace($node->name);
|
||||
} elseif ($node instanceof Stmt\Use_) {
|
||||
foreach ($node->uses as $use) {
|
||||
$this->addAlias($use, $node->type, null);
|
||||
}
|
||||
} elseif ($node instanceof Stmt\GroupUse) {
|
||||
foreach ($node->uses as $use) {
|
||||
$this->addAlias($use, $node->type, $node->prefix);
|
||||
}
|
||||
} elseif ($node instanceof Stmt\Class_) {
|
||||
if (null !== $node->extends) {
|
||||
$node->extends = $this->resolveClassName($node->extends);
|
||||
}
|
||||
|
||||
foreach ($node->implements as &$interface) {
|
||||
$interface = $this->resolveClassName($interface);
|
||||
}
|
||||
|
||||
$this->resolveAttrGroups($node);
|
||||
if (null !== $node->name) {
|
||||
$this->addNamespacedName($node);
|
||||
} else {
|
||||
$node->namespacedName = null;
|
||||
}
|
||||
} elseif ($node instanceof Stmt\Interface_) {
|
||||
foreach ($node->extends as &$interface) {
|
||||
$interface = $this->resolveClassName($interface);
|
||||
}
|
||||
|
||||
$this->resolveAttrGroups($node);
|
||||
$this->addNamespacedName($node);
|
||||
} elseif ($node instanceof Stmt\Enum_) {
|
||||
foreach ($node->implements as &$interface) {
|
||||
$interface = $this->resolveClassName($interface);
|
||||
}
|
||||
|
||||
$this->resolveAttrGroups($node);
|
||||
$this->addNamespacedName($node);
|
||||
} elseif ($node instanceof Stmt\Trait_) {
|
||||
$this->resolveAttrGroups($node);
|
||||
$this->addNamespacedName($node);
|
||||
} elseif ($node instanceof Stmt\Function_) {
|
||||
$this->resolveSignature($node);
|
||||
$this->resolveAttrGroups($node);
|
||||
$this->addNamespacedName($node);
|
||||
} elseif ($node instanceof Stmt\ClassMethod
|
||||
|| $node instanceof Expr\Closure
|
||||
|| $node instanceof Expr\ArrowFunction
|
||||
) {
|
||||
$this->resolveSignature($node);
|
||||
$this->resolveAttrGroups($node);
|
||||
} elseif ($node instanceof Stmt\Property) {
|
||||
if (null !== $node->type) {
|
||||
$node->type = $this->resolveType($node->type);
|
||||
}
|
||||
$this->resolveAttrGroups($node);
|
||||
} elseif ($node instanceof Node\PropertyHook) {
|
||||
foreach ($node->params as $param) {
|
||||
$param->type = $this->resolveType($param->type);
|
||||
$this->resolveAttrGroups($param);
|
||||
}
|
||||
$this->resolveAttrGroups($node);
|
||||
} elseif ($node instanceof Stmt\Const_) {
|
||||
foreach ($node->consts as $const) {
|
||||
$this->addNamespacedName($const);
|
||||
}
|
||||
} elseif ($node instanceof Stmt\ClassConst) {
|
||||
if (null !== $node->type) {
|
||||
$node->type = $this->resolveType($node->type);
|
||||
}
|
||||
$this->resolveAttrGroups($node);
|
||||
} elseif ($node instanceof Stmt\EnumCase) {
|
||||
$this->resolveAttrGroups($node);
|
||||
} elseif ($node instanceof Expr\StaticCall
|
||||
|| $node instanceof Expr\StaticPropertyFetch
|
||||
|| $node instanceof Expr\ClassConstFetch
|
||||
|| $node instanceof Expr\New_
|
||||
|| $node instanceof Expr\Instanceof_
|
||||
) {
|
||||
if ($node->class instanceof Name) {
|
||||
$node->class = $this->resolveClassName($node->class);
|
||||
}
|
||||
} elseif ($node instanceof Stmt\Catch_) {
|
||||
foreach ($node->types as &$type) {
|
||||
$type = $this->resolveClassName($type);
|
||||
}
|
||||
} elseif ($node instanceof Expr\FuncCall) {
|
||||
if ($node->name instanceof Name) {
|
||||
$node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_FUNCTION);
|
||||
}
|
||||
} elseif ($node instanceof Expr\ConstFetch) {
|
||||
$node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_CONSTANT);
|
||||
} elseif ($node instanceof Stmt\TraitUse) {
|
||||
foreach ($node->traits as &$trait) {
|
||||
$trait = $this->resolveClassName($trait);
|
||||
}
|
||||
|
||||
foreach ($node->adaptations as $adaptation) {
|
||||
if (null !== $adaptation->trait) {
|
||||
$adaptation->trait = $this->resolveClassName($adaptation->trait);
|
||||
}
|
||||
|
||||
if ($adaptation instanceof Stmt\TraitUseAdaptation\Precedence) {
|
||||
foreach ($adaptation->insteadof as &$insteadof) {
|
||||
$insteadof = $this->resolveClassName($insteadof);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @param Stmt\Use_::TYPE_* $type */
|
||||
private function addAlias(Node\UseItem $use, int $type, ?Name $prefix = null): void {
|
||||
// Add prefix for group uses
|
||||
$name = $prefix ? Name::concat($prefix, $use->name) : $use->name;
|
||||
// Type is determined either by individual element or whole use declaration
|
||||
$type |= $use->type;
|
||||
|
||||
$this->nameContext->addAlias(
|
||||
$name, (string) $use->getAlias(), $type, $use->getAttributes()
|
||||
);
|
||||
}
|
||||
|
||||
/** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure|Expr\ArrowFunction $node */
|
||||
private function resolveSignature($node): void {
|
||||
foreach ($node->params as $param) {
|
||||
$param->type = $this->resolveType($param->type);
|
||||
$this->resolveAttrGroups($param);
|
||||
}
|
||||
$node->returnType = $this->resolveType($node->returnType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T of Node\Identifier|Name|Node\ComplexType|null
|
||||
* @param T $node
|
||||
* @return T
|
||||
*/
|
||||
private function resolveType(?Node $node): ?Node {
|
||||
if ($node instanceof Name) {
|
||||
return $this->resolveClassName($node);
|
||||
}
|
||||
if ($node instanceof Node\NullableType) {
|
||||
$node->type = $this->resolveType($node->type);
|
||||
return $node;
|
||||
}
|
||||
if ($node instanceof Node\UnionType || $node instanceof Node\IntersectionType) {
|
||||
foreach ($node->types as &$type) {
|
||||
$type = $this->resolveType($type);
|
||||
}
|
||||
return $node;
|
||||
}
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve name, according to name resolver options.
|
||||
*
|
||||
* @param Name $name Function or constant name to resolve
|
||||
* @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_*
|
||||
*
|
||||
* @return Name Resolved name, or original name with attribute
|
||||
*/
|
||||
protected function resolveName(Name $name, int $type): Name {
|
||||
if (!$this->replaceNodes) {
|
||||
$resolvedName = $this->nameContext->getResolvedName($name, $type);
|
||||
if (null !== $resolvedName) {
|
||||
$name->setAttribute('resolvedName', $resolvedName);
|
||||
} else {
|
||||
$name->setAttribute('namespacedName', FullyQualified::concat(
|
||||
$this->nameContext->getNamespace(), $name, $name->getAttributes()));
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
if ($this->preserveOriginalNames) {
|
||||
// Save the original name
|
||||
$originalName = $name;
|
||||
$name = clone $originalName;
|
||||
$name->setAttribute('originalName', $originalName);
|
||||
}
|
||||
|
||||
$resolvedName = $this->nameContext->getResolvedName($name, $type);
|
||||
if (null !== $resolvedName) {
|
||||
return $resolvedName;
|
||||
}
|
||||
|
||||
// unqualified names inside a namespace cannot be resolved at compile-time
|
||||
// add the namespaced version of the name as an attribute
|
||||
$name->setAttribute('namespacedName', FullyQualified::concat(
|
||||
$this->nameContext->getNamespace(), $name, $name->getAttributes()));
|
||||
return $name;
|
||||
}
|
||||
|
||||
protected function resolveClassName(Name $name): Name {
|
||||
return $this->resolveName($name, Stmt\Use_::TYPE_NORMAL);
|
||||
}
|
||||
|
||||
protected function addNamespacedName(Node $node): void {
|
||||
$node->namespacedName = Name::concat(
|
||||
$this->nameContext->getNamespace(), (string) $node->name);
|
||||
}
|
||||
|
||||
protected function resolveAttrGroups(Node $node): void {
|
||||
foreach ($node->attrGroups as $attrGroup) {
|
||||
foreach ($attrGroup->attrs as $attr) {
|
||||
$attr->name = $this->resolveClassName($attr->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2792
File diff suppressed because it is too large
Load Diff
+2790
File diff suppressed because it is too large
Load Diff
+1286
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,164 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
/**
|
||||
* A PHP version, representing only the major and minor version components.
|
||||
*/
|
||||
class PhpVersion {
|
||||
/** @var int Version ID in PHP_VERSION_ID format */
|
||||
public int $id;
|
||||
|
||||
/** @var int[] Minimum versions for builtin types */
|
||||
private const BUILTIN_TYPE_VERSIONS = [
|
||||
'array' => 50100,
|
||||
'callable' => 50400,
|
||||
'bool' => 70000,
|
||||
'int' => 70000,
|
||||
'float' => 70000,
|
||||
'string' => 70000,
|
||||
'iterable' => 70100,
|
||||
'void' => 70100,
|
||||
'object' => 70200,
|
||||
'null' => 80000,
|
||||
'false' => 80000,
|
||||
'mixed' => 80000,
|
||||
'never' => 80100,
|
||||
'true' => 80200,
|
||||
];
|
||||
|
||||
private function __construct(int $id) {
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a PhpVersion object from major and minor version components.
|
||||
*/
|
||||
public static function fromComponents(int $major, int $minor): self {
|
||||
return new self($major * 10000 + $minor * 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the newest PHP version supported by this library. Support for this version may be partial,
|
||||
* if it is still under development.
|
||||
*/
|
||||
public static function getNewestSupported(): self {
|
||||
return self::fromComponents(8, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the host PHP version, that is the PHP version we're currently running on.
|
||||
*/
|
||||
public static function getHostVersion(): self {
|
||||
return self::fromComponents(\PHP_MAJOR_VERSION, \PHP_MINOR_VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the version from a string like "8.1".
|
||||
*/
|
||||
public static function fromString(string $version): self {
|
||||
if (!preg_match('/^(\d+)\.(\d+)/', $version, $matches)) {
|
||||
throw new \LogicException("Invalid PHP version \"$version\"");
|
||||
}
|
||||
return self::fromComponents((int) $matches[1], (int) $matches[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether two versions are the same.
|
||||
*/
|
||||
public function equals(PhpVersion $other): bool {
|
||||
return $this->id === $other->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this version is greater than or equal to the argument.
|
||||
*/
|
||||
public function newerOrEqual(PhpVersion $other): bool {
|
||||
return $this->id >= $other->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this version is older than the argument.
|
||||
*/
|
||||
public function older(PhpVersion $other): bool {
|
||||
return $this->id < $other->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this is the host PHP version.
|
||||
*/
|
||||
public function isHostVersion(): bool {
|
||||
return $this->equals(self::getHostVersion());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this PHP version supports the given builtin type. Type name must be lowercase.
|
||||
*/
|
||||
public function supportsBuiltinType(string $type): bool {
|
||||
$minVersion = self::BUILTIN_TYPE_VERSIONS[$type] ?? null;
|
||||
return $minVersion !== null && $this->id >= $minVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this version supports [] array literals.
|
||||
*/
|
||||
public function supportsShortArraySyntax(): bool {
|
||||
return $this->id >= 50400;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this version supports [] for destructuring.
|
||||
*/
|
||||
public function supportsShortArrayDestructuring(): bool {
|
||||
return $this->id >= 70100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this version supports flexible heredoc/nowdoc.
|
||||
*/
|
||||
public function supportsFlexibleHeredoc(): bool {
|
||||
return $this->id >= 70300;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this version supports trailing commas in parameter lists.
|
||||
*/
|
||||
public function supportsTrailingCommaInParamList(): bool {
|
||||
return $this->id >= 80000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this version allows "$var =& new Obj".
|
||||
*/
|
||||
public function allowsAssignNewByReference(): bool {
|
||||
return $this->id < 70000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this version allows invalid octals like "08".
|
||||
*/
|
||||
public function allowsInvalidOctals(): bool {
|
||||
return $this->id < 70000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this version allows DEL (\x7f) to occur in identifiers.
|
||||
*/
|
||||
public function allowsDelInIdentifiers(): bool {
|
||||
return $this->id < 70100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this version supports yield in expression context without parentheses.
|
||||
*/
|
||||
public function supportsYieldWithoutParentheses(): bool {
|
||||
return $this->id >= 70000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this version supports unicode escape sequences in strings.
|
||||
*/
|
||||
public function supportsUnicodeEscapes(): bool {
|
||||
return $this->id >= 70000;
|
||||
}
|
||||
}
|
||||
+1192
File diff suppressed because it is too large
Load Diff
+1693
File diff suppressed because it is too large
Load Diff
+68
@@ -0,0 +1,68 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
if (!\function_exists('PhpParser\defineCompatibilityTokens')) {
|
||||
function defineCompatibilityTokens(): void {
|
||||
$compatTokens = [
|
||||
// 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 8.4
|
||||
'T_PROPERTY_C',
|
||||
'T_PUBLIC_SET',
|
||||
'T_PROTECTED_SET',
|
||||
'T_PRIVATE_SET',
|
||||
];
|
||||
|
||||
// PHP-Parser might be used together with another library that also emulates some or all
|
||||
// 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);
|
||||
if (!\is_int($tokenId)) {
|
||||
throw new \Error(sprintf(
|
||||
'Token %s has ID of type %s, should be int. ' .
|
||||
'You may be using a library with broken token emulation',
|
||||
$token, \gettype($tokenId)
|
||||
));
|
||||
}
|
||||
$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--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defineCompatibilityTokens();
|
||||
}
|
||||
Reference in New Issue
Block a user