mirror of
https://github.com/nikic/PHP-Parser.git
synced 2025-07-09 16:36:31 +02:00
Compare commits
63 Commits
v4.0.0beta
...
v4.0.4
Author | SHA1 | Date | |
---|---|---|---|
fa6ee28600 | |||
16c5d107ca | |||
d7d581c1d2 | |||
1973b6633a | |||
7f96481c80 | |||
41408081d7 | |||
4d6825c460 | |||
d4a5a6e84b | |||
c55798ccc6 | |||
82e8b33814 | |||
8d9ae46597 | |||
f6fc248ca3 | |||
84dcd179d2 | |||
3e02eb7108 | |||
2f67429ec9 | |||
0cd7207ca6 | |||
6751ac3c9d | |||
5aeb884ab5 | |||
b855c1ca23 | |||
53c732a96d | |||
bd088dc940 | |||
8b1c9c0409 | |||
df64d86cf4 | |||
aac539ef0a | |||
f967b867d5 | |||
6bbdaf6c16 | |||
35b8caf75e | |||
6526ea2497 | |||
3ce5e628a7 | |||
ee870e3464 | |||
21d27527ed | |||
7b201b63d2 | |||
01e85a26c6 | |||
e6452e8d15 | |||
9202d87f53 | |||
fa99c3fbfb | |||
2b0bd657bb | |||
81b7be3ba6 | |||
6121001576 | |||
7208b1c7ac | |||
e4a54fa90a | |||
3125b54e5e | |||
7f8ff1b9a4 | |||
91a462ce76 | |||
610617fe93 | |||
ff2d85dc6b | |||
b998d1e9b5 | |||
6aba7624ed | |||
9c18e3db49 | |||
ae52aadb43 | |||
9cea94000a | |||
bb87e28e7d | |||
7484acb88b | |||
bc5ba47b28 | |||
b9996315a6 | |||
de3470190c | |||
1c3eabb000 | |||
8d1e86b47f | |||
ff10cc9d08 | |||
14454326e9 | |||
3a45c1a121 | |||
ad9c42b66a | |||
08215e7646 |
75
CHANGELOG.md
75
CHANGELOG.md
@ -1,8 +1,72 @@
|
|||||||
Version 4.0.0-dev
|
Version 4.0.5-dev
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
Nothing yet.
|
Nothing yet.
|
||||||
|
|
||||||
|
Version 4.0.4 (2018-09-18)
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* The following methods have been added to `BuilderFactory`:
|
||||||
|
* `useTrait()` (fluent builder)
|
||||||
|
* `traitUseAdaptation()` (fluent builder)
|
||||||
|
* `useFunction()` (fluent builder)
|
||||||
|
* `useConst()` (fluent builder)
|
||||||
|
* `var()`
|
||||||
|
* `propertyFetch()`
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
|
||||||
|
* `Builder\Param::setTypeHint()` has been deprecated in favor of the newly introduced
|
||||||
|
`Builder\Param::setType()`.
|
||||||
|
|
||||||
|
Version 4.0.3 (2018-07-15)
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fixed possible undefined offset notice in formatting-preserving printer. (#513)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Improved error recovery inside arrays.
|
||||||
|
* Preserve trailing comment inside classes. **Note:** This change is possibly BC breaking if your
|
||||||
|
code validates that classes can only contain certain statement types. After this change, classes
|
||||||
|
can also contain Nop statements, while this was not previously possible. (#509)
|
||||||
|
|
||||||
|
Version 4.0.2 (2018-06-03)
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Improved error recovery inside classes.
|
||||||
|
* Support error recovery for `foreach` without `as`.
|
||||||
|
* Support error recovery for parameters without variable (`function (Type ) {}`).
|
||||||
|
* Support error recovery for functions without body (`function ($foo)`).
|
||||||
|
|
||||||
|
Version 4.0.1 (2018-03-25)
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* [PHP 7.3] Added support for trailing commas in function calls.
|
||||||
|
* [PHP 7.3] Added support for by-reference array destructuring.
|
||||||
|
* Added checks to node traverser to prevent replacing a statement with an expression or vice versa.
|
||||||
|
This should prevent common mistakes in the implementation of node visitors.
|
||||||
|
* Added the following methods to `BuilderFactory`, to simplify creation of expressions:
|
||||||
|
* `funcCall()`
|
||||||
|
* `methodCall()`
|
||||||
|
* `staticCall()`
|
||||||
|
* `new()`
|
||||||
|
* `constFetch()`
|
||||||
|
* `classConstFetch()`
|
||||||
|
|
||||||
|
Version 4.0.0 (2018-02-28)
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
* No significant code changes since the beta 1 release.
|
||||||
|
|
||||||
Version 4.0.0-beta1 (2018-01-27)
|
Version 4.0.0-beta1 (2018-01-27)
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
@ -108,10 +172,13 @@ Version 4.0.0-alpha1 (2017-10-18)
|
|||||||
* The `BuilderAbstract` class has been removed. It's functionality is moved into `BuilderHelpers`.
|
* The `BuilderAbstract` class has been removed. It's functionality is moved into `BuilderHelpers`.
|
||||||
However, this is an internal class and should not be used directly.
|
However, this is an internal class and should not be used directly.
|
||||||
|
|
||||||
Version 3.1.5-dev
|
Version 3.1.5 (2018-02-28)
|
||||||
-----------------
|
--------------------------
|
||||||
|
|
||||||
Nothing yet.
|
### Fixed
|
||||||
|
|
||||||
|
* Fixed duplicate comment assignment in switch statements. (#469)
|
||||||
|
* Improve compatibility with PHP-Scoper. (#477)
|
||||||
|
|
||||||
Version 3.1.4 (2018-01-25)
|
Version 3.1.4 (2018-01-25)
|
||||||
--------------------------
|
--------------------------
|
||||||
|
20
README.md
20
README.md
@ -6,9 +6,9 @@ PHP Parser
|
|||||||
This is a PHP 5.2 to PHP 7.2 parser written in PHP. Its purpose is to simplify static code analysis and
|
This is a PHP 5.2 to PHP 7.2 parser written in PHP. Its purpose is to simplify static code analysis and
|
||||||
manipulation.
|
manipulation.
|
||||||
|
|
||||||
[**Documentation for version 3.x**][doc_3_x] (stable; for running on PHP >= 5.5; for parsing PHP 5.2 to PHP 7.2).
|
[**Documentation for version 4.x**][doc_master] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 7.2).
|
||||||
|
|
||||||
[Documentation for version 4.x][doc_master] (beta; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 7.2).
|
[Documentation for version 3.x][doc_3_x] (unsupported; for running on PHP >= 5.5; for parsing PHP 5.2 to PHP 7.2).
|
||||||
|
|
||||||
Features
|
Features
|
||||||
--------
|
--------
|
||||||
@ -183,10 +183,16 @@ Documentation
|
|||||||
|
|
||||||
1. [Introduction](doc/0_Introduction.markdown)
|
1. [Introduction](doc/0_Introduction.markdown)
|
||||||
2. [Usage of basic components](doc/2_Usage_of_basic_components.markdown)
|
2. [Usage of basic components](doc/2_Usage_of_basic_components.markdown)
|
||||||
3. [Other node tree representations](doc/3_Other_node_tree_representations.markdown)
|
|
||||||
|
|
||||||
Component documentation:
|
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 resolution](doc/component/Name_resolution.markdown)
|
||||||
* Name resolver options
|
* Name resolver options
|
||||||
* Name resolution context
|
* Name resolution context
|
||||||
@ -194,7 +200,7 @@ Component documentation:
|
|||||||
* Converting AST back to PHP code
|
* Converting AST back to PHP code
|
||||||
* Customizing formatting
|
* Customizing formatting
|
||||||
* Formatting-preserving code transformations
|
* Formatting-preserving code transformations
|
||||||
* [AST builders](component/AST_builders.markdown)
|
* [AST builders](doc/component/AST_builders.markdown)
|
||||||
* Fluent builders for AST nodes
|
* Fluent builders for AST nodes
|
||||||
* [Lexer](doc/component/Lexer.markdown)
|
* [Lexer](doc/component/Lexer.markdown)
|
||||||
* Lexer options
|
* Lexer options
|
||||||
@ -203,14 +209,16 @@ Component documentation:
|
|||||||
* [Error handling](doc/component/Error_handling.markdown)
|
* [Error handling](doc/component/Error_handling.markdown)
|
||||||
* Column information for errors
|
* Column information for errors
|
||||||
* Error recovery (parsing of syntactically incorrect code)
|
* Error recovery (parsing of syntactically incorrect code)
|
||||||
* [Constant expression evaluation](component/Constant_expression_evaluation.markdown)
|
* [Constant expression evaluation](doc/component/Constant_expression_evaluation.markdown)
|
||||||
* Evaluating constant/property/etc initializers
|
* Evaluating constant/property/etc initializers
|
||||||
* Handling errors and unsupported expressions
|
* Handling errors and unsupported expressions
|
||||||
|
* [JSON representation](doc/component/JSON_representation.markdown)
|
||||||
|
* JSON encoding and decoding of ASTs
|
||||||
* [Performance](doc/component/Performance.markdown)
|
* [Performance](doc/component/Performance.markdown)
|
||||||
* Disabling XDebug
|
* Disabling XDebug
|
||||||
* Reusing objects
|
* Reusing objects
|
||||||
* Garbage collection impact
|
* Garbage collection impact
|
||||||
* [Frequently asked questions](component/FAQ.markdown)
|
* [Frequently asked questions](doc/component/FAQ.markdown)
|
||||||
* Parent and sibling references
|
* Parent and sibling references
|
||||||
|
|
||||||
[doc_3_x]: https://github.com/nikic/PHP-Parser/tree/3.x/doc
|
[doc_3_x]: https://github.com/nikic/PHP-Parser/tree/3.x/doc
|
||||||
|
@ -4,7 +4,7 @@ Upgrading from PHP-Parser 2.x to 3.0
|
|||||||
The backwards-incompatible changes in this release may be summarized as follows:
|
The backwards-incompatible changes in this release may be summarized as follows:
|
||||||
|
|
||||||
* The specific details of the node representation have changed in some cases, primarily to
|
* The specific details of the node representation have changed in some cases, primarily to
|
||||||
accomodate new PHP 7.1 features.
|
accommodate new PHP 7.1 features.
|
||||||
* There have been significant changes to the error recovery implementation. This may affect you,
|
* There have been significant changes to the error recovery implementation. This may affect you,
|
||||||
if you used the error recovery mode or have a custom lexer implementation.
|
if you used the error recovery mode or have a custom lexer implementation.
|
||||||
* A number of deprecated methods were removed.
|
* A number of deprecated methods were removed.
|
||||||
|
@ -6,7 +6,7 @@ Upgrading from PHP-Parser 3.x to 4.0
|
|||||||
PHP-Parser now requires PHP 7.0 or newer to run. It is however still possible to *parse* PHP 5.2-5.6
|
PHP-Parser now requires PHP 7.0 or newer to run. It is however still possible to *parse* PHP 5.2-5.6
|
||||||
source code, while running on a newer version.
|
source code, while running on a newer version.
|
||||||
|
|
||||||
Because HHVM does not support PHP 7, HHVM is no longer supported.
|
HHVM is no longer actively supported.
|
||||||
|
|
||||||
### Changes to the node structure
|
### Changes to the node structure
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ Because HHVM does not support PHP 7, HHVM is no longer supported.
|
|||||||
comparisons or strict-mode may require adjustment. The following is an exhaustive list of all
|
comparisons or strict-mode may require adjustment. The following is an exhaustive list of all
|
||||||
affected subnodes:
|
affected subnodes:
|
||||||
|
|
||||||
* `Const::$name`
|
* `Const_::$name`
|
||||||
* `NullableType::$type` (for simple types)
|
* `NullableType::$type` (for simple types)
|
||||||
* `Param::$type` (for simple types)
|
* `Param::$type` (for simple types)
|
||||||
* `Expr\ClassConstFetch::$name`
|
* `Expr\ClassConstFetch::$name`
|
||||||
@ -29,8 +29,8 @@ Because HHVM does not support PHP 7, HHVM is no longer supported.
|
|||||||
* `Stmt\Class_::$name`
|
* `Stmt\Class_::$name`
|
||||||
* `Stmt\ClassMethod::$name`
|
* `Stmt\ClassMethod::$name`
|
||||||
* `Stmt\ClassMethod::$returnType` (for simple types)
|
* `Stmt\ClassMethod::$returnType` (for simple types)
|
||||||
* `Stmt\Function::$name`
|
* `Stmt\Function_::$name`
|
||||||
* `Stmt\Function::$returnType` (for simple types)
|
* `Stmt\Function_::$returnType` (for simple types)
|
||||||
* `Stmt\Goto_::$name`
|
* `Stmt\Goto_::$name`
|
||||||
* `Stmt\Interface_::$name`
|
* `Stmt\Interface_::$name`
|
||||||
* `Stmt\Label::$name`
|
* `Stmt\Label::$name`
|
||||||
@ -48,7 +48,7 @@ Because HHVM does not support PHP 7, HHVM is no longer supported.
|
|||||||
* The `name` subnode of `StaticVar` has been renamed to `var` and now contains a `Variable` rather
|
* The `name` subnode of `StaticVar` has been renamed to `var` and now contains a `Variable` rather
|
||||||
than a plain string.
|
than a plain string.
|
||||||
* The `var` subnode of `ClosureUse` now contains a `Variable` rather than a plain string.
|
* The `var` subnode of `ClosureUse` now contains a `Variable` rather than a plain string.
|
||||||
* The `var` subnode of `Catch` now contains a `Variable` rather than a plain string.
|
* The `var` subnode of `Catch_` now contains a `Variable` rather than a plain string.
|
||||||
* The `alias` subnode of `UseUse` is now `null` if no explicit alias is given. As such,
|
* The `alias` subnode of `UseUse` is now `null` if no explicit alias is given. As such,
|
||||||
`use Foo\Bar` and `use Foo\Bar as Bar` are now represented differently. The `getAlias()` method
|
`use Foo\Bar` and `use Foo\Bar as Bar` are now represented differently. The `getAlias()` method
|
||||||
can be used to get the effective alias, even if it is not explicitly given.
|
can be used to get the effective alias, even if it is not explicitly given.
|
||||||
@ -62,7 +62,7 @@ Because HHVM does not support PHP 7, HHVM is no longer supported.
|
|||||||
|
|
||||||
### Removed functionality
|
### Removed functionality
|
||||||
|
|
||||||
* Removed `type` subnode on `Class`, `ClassMethod` and `Property` nodes. Use `flags` instead.
|
* Removed `type` subnode on `Class_`, `ClassMethod` and `Property` nodes. Use `flags` instead.
|
||||||
* The `ClassConst::isStatic()` method has been removed. Constants cannot have a static modifier.
|
* The `ClassConst::isStatic()` method has been removed. Constants cannot have a static modifier.
|
||||||
* The `NodeTraverser` no longer accepts `false` as a return value from a `leaveNode()` method.
|
* The `NodeTraverser` no longer accepts `false` as a return value from a `leaveNode()` method.
|
||||||
`NodeTraverser::REMOVE_NODE` should be returned instead.
|
`NodeTraverser::REMOVE_NODE` should be returned instead.
|
||||||
@ -74,4 +74,4 @@ Because HHVM does not support PHP 7, HHVM is no longer supported.
|
|||||||
`Unserializer\XML`, as well as the interfaces `Serializer` and `Unserializer` no longer exist.
|
`Unserializer\XML`, as well as the interfaces `Serializer` and `Unserializer` no longer exist.
|
||||||
* The `BuilderAbstract` class has been removed. It's functionality is moved into `BuilderHelpers`.
|
* The `BuilderAbstract` class has been removed. It's functionality is moved into `BuilderHelpers`.
|
||||||
However, this is an internal class and should not be used directly.
|
However, this is an internal class and should not be used directly.
|
||||||
* The `Autoloader` class has been removed in favor of relying on the Composer autoloader.
|
* The `Autoloader` class has been removed in favor of relying on the Composer autoloader.
|
||||||
|
@ -81,6 +81,9 @@ foreach ($files as $file) {
|
|||||||
} elseif ('pretty-print' === $operation) {
|
} elseif ('pretty-print' === $operation) {
|
||||||
echo "==> Pretty print:\n";
|
echo "==> Pretty print:\n";
|
||||||
echo $prettyPrinter->prettyPrintFile($stmts), "\n";
|
echo $prettyPrinter->prettyPrintFile($stmts), "\n";
|
||||||
|
} elseif ('json-dump' === $operation) {
|
||||||
|
echo "==> JSON dump:\n";
|
||||||
|
echo json_encode($stmts, JSON_PRETTY_PRINT), "\n";
|
||||||
} elseif ('var-dump' === $operation) {
|
} elseif ('var-dump' === $operation) {
|
||||||
echo "==> var_dump():\n";
|
echo "==> var_dump():\n";
|
||||||
var_dump($stmts);
|
var_dump($stmts);
|
||||||
@ -112,6 +115,7 @@ Operations is a list of the following options (--dump by default):
|
|||||||
|
|
||||||
-d, --dump Dump nodes using NodeDumper
|
-d, --dump Dump nodes using NodeDumper
|
||||||
-p, --pretty-print Pretty print file using PrettyPrinter\Standard
|
-p, --pretty-print Pretty print file using PrettyPrinter\Standard
|
||||||
|
-j, --json-dump Print json_encode() result
|
||||||
--var-dump var_dump() nodes (for exact structure)
|
--var-dump var_dump() nodes (for exact structure)
|
||||||
-N, --resolve-names Resolve names using NodeVisitor\NameResolver
|
-N, --resolve-names Resolve names using NodeVisitor\NameResolver
|
||||||
-c, --with-column-info Show column-numbers for errors (if available)
|
-c, --with-column-info Show column-numbers for errors (if available)
|
||||||
@ -155,8 +159,9 @@ function parseArgs($args) {
|
|||||||
case '-p':
|
case '-p':
|
||||||
$operations[] = 'pretty-print';
|
$operations[] = 'pretty-print';
|
||||||
break;
|
break;
|
||||||
case '--serialize-xml':
|
case '--json-dump':
|
||||||
$operations[] = 'serialize-xml';
|
case '-j':
|
||||||
|
$operations[] = 'json-dump';
|
||||||
break;
|
break;
|
||||||
case '--var-dump':
|
case '--var-dump':
|
||||||
$operations[] = 'var-dump';
|
$operations[] = 'var-dump';
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
"ext-tokenizer": "*"
|
"ext-tokenizer": "*"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^6"
|
"phpunit/phpunit": "^6.5 || ^7.0"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
@ -14,7 +14,7 @@ There are other ways of processing source code. One that PHP supports natively i
|
|||||||
token stream generated by [`token_get_all`][2]. The token stream is much more low level than
|
token stream generated by [`token_get_all`][2]. The token stream is much more low level than
|
||||||
the AST and thus has different applications: It allows to also analyze the exact formatting of
|
the AST and thus has different applications: It allows to also analyze the exact formatting of
|
||||||
a file. On the other hand the token stream is much harder to deal with for more complex analysis.
|
a file. On the other hand the token stream is much harder to deal with for more complex analysis.
|
||||||
For example an AST abstracts away the fact that in PHP variables can be written as `$foo`, but also
|
For example, an AST abstracts away the fact that, in PHP, variables can be written as `$foo`, but also
|
||||||
as `$$bar`, `${'foobar'}` or even `${!${''}=barfoo()}`. You don't have to worry about recognizing
|
as `$$bar`, `${'foobar'}` or even `${!${''}=barfoo()}`. You don't have to worry about recognizing
|
||||||
all the different syntaxes from a stream of tokens.
|
all the different syntaxes from a stream of tokens.
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ hacky and not perfect, but it should work well on any sane code.
|
|||||||
What output does it produce?
|
What output does it produce?
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
The parser produces an [Abstract Syntax Tree][1] (AST) also known as a node tree. How this looks like
|
The parser produces an [Abstract Syntax Tree][1] (AST) also known as a node tree. How this looks
|
||||||
can best be seen in an example. The program `<?php echo 'Hi', 'World';` will give you a node tree
|
can best be seen in an example. The program `<?php echo 'Hi', 'World';` will give you a node tree
|
||||||
roughly looking like this:
|
roughly looking like this:
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ Kind | Behavior
|
|||||||
`ParserFactory::ONLY_PHP7` | Parse code as PHP 7.
|
`ParserFactory::ONLY_PHP7` | Parse code as PHP 7.
|
||||||
`ParserFactory::ONLY_PHP5` | Parse code as PHP 5.
|
`ParserFactory::ONLY_PHP5` | Parse code as PHP 5.
|
||||||
|
|
||||||
Unless you have strong reason to use something else, `PREFER_PHP7` is a reasonable default.
|
Unless you have a strong reason to use something else, `PREFER_PHP7` is a reasonable default.
|
||||||
|
|
||||||
The `create()` method optionally accepts a `Lexer` instance as the second argument. Some use cases
|
The `create()` method optionally accepts a `Lexer` instance as the second argument. Some use cases
|
||||||
that require customized lexers are discussed in the [lexer documentation](component/Lexer.markdown).
|
that require customized lexers are discussed in the [lexer documentation](component/Lexer.markdown).
|
||||||
@ -50,10 +50,18 @@ Subsequently you can pass PHP code (including the opening `<?php` tag) to the `p
|
|||||||
create a syntax tree. If a syntax error is encountered, an `PhpParser\Error` exception will be thrown:
|
create a syntax tree. If a syntax error is encountered, an `PhpParser\Error` exception will be thrown:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
|
<?php
|
||||||
use PhpParser\Error;
|
use PhpParser\Error;
|
||||||
use PhpParser\ParserFactory;
|
use PhpParser\ParserFactory;
|
||||||
|
|
||||||
$code = '<?php // some code';
|
$code = <<<'CODE'
|
||||||
|
<?php
|
||||||
|
function printLine($msg) {
|
||||||
|
echo $msg, "\n";
|
||||||
|
}
|
||||||
|
printLine('Hello World!!!');
|
||||||
|
CODE;
|
||||||
|
|
||||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -66,27 +74,68 @@ try {
|
|||||||
|
|
||||||
A parser instance can be reused to parse multiple files.
|
A parser instance can be reused to parse multiple files.
|
||||||
|
|
||||||
Node tree
|
Node dumping
|
||||||
---------
|
------------
|
||||||
|
|
||||||
If you use the above code with `$code = "<?php echo 'Hi ', hi\\getTarget();"` the parser will
|
To dump the abstact syntax tree in human readable form, a `NodeDumper` can be used:
|
||||||
generate a node tree looking like this:
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
use PhpParser\NodeDumper;
|
||||||
|
|
||||||
|
$nodeDumper = new NodeDumper;
|
||||||
|
echo $nodeDumper->dump($stmts), "\n";
|
||||||
|
```
|
||||||
|
|
||||||
|
For the sample code from the previous section, this will produce the following output:
|
||||||
|
|
||||||
```
|
```
|
||||||
array(
|
array(
|
||||||
0: Stmt_Echo(
|
0: Stmt_Function(
|
||||||
exprs: array(
|
byRef: false
|
||||||
0: Scalar_String(
|
name: Identifier(
|
||||||
value: Hi
|
name: printLine
|
||||||
|
)
|
||||||
|
params: array(
|
||||||
|
0: Param(
|
||||||
|
type: null
|
||||||
|
byRef: false
|
||||||
|
variadic: false
|
||||||
|
var: Expr_Variable(
|
||||||
|
name: msg
|
||||||
|
)
|
||||||
|
default: null
|
||||||
)
|
)
|
||||||
1: Expr_FuncCall(
|
)
|
||||||
name: Name(
|
returnType: null
|
||||||
parts: array(
|
stmts: array(
|
||||||
0: hi
|
0: Stmt_Echo(
|
||||||
1: getTarget
|
exprs: array(
|
||||||
|
0: Expr_Variable(
|
||||||
|
name: msg
|
||||||
|
)
|
||||||
|
1: Scalar_String(
|
||||||
|
value:
|
||||||
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
args: array(
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
1: Stmt_Expression(
|
||||||
|
expr: Expr_FuncCall(
|
||||||
|
name: Name(
|
||||||
|
parts: array(
|
||||||
|
0: printLine
|
||||||
|
)
|
||||||
|
)
|
||||||
|
args: array(
|
||||||
|
0: Arg(
|
||||||
|
value: Scalar_String(
|
||||||
|
value: Hello World!!!
|
||||||
|
)
|
||||||
|
byRef: false
|
||||||
|
unpack: false
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -94,10 +143,30 @@ array(
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
Thus `$stmts` will contain an array with only one node, with this node being an instance of
|
You can also use the `php-parse` script to obtain such a node dump by calling it either with a file
|
||||||
`PhpParser\Node\Stmt\Echo_`.
|
name or code string:
|
||||||
|
|
||||||
As PHP is a large language there are approximately 140 different nodes. In order to make work
|
```sh
|
||||||
|
vendor/bin/php-parse file.php
|
||||||
|
vendor/bin/php-parse "<?php foo();"
|
||||||
|
```
|
||||||
|
|
||||||
|
This can be very helpful if you want to quickly check how certain syntax is represented in the AST.
|
||||||
|
|
||||||
|
Node tree structure
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Looking at the node dump above, you can see that `$stmts` for this example code is an array of two
|
||||||
|
nodes, a `Stmt_Function` and a `Stmt_Expression`. The corresponding class names are:
|
||||||
|
|
||||||
|
* `Stmt_Function -> PhpParser\Node\Stmt\Function_`
|
||||||
|
* `Stmt_Expression -> PhpParser\Node\Stmt\Expression`
|
||||||
|
|
||||||
|
The additional `_` at the end of the first class name is necessary, because `Function` is a
|
||||||
|
reserved keyword. Many node class names in this library have a trailing `_` to avoid clashing with
|
||||||
|
a keyword.
|
||||||
|
|
||||||
|
As PHP is a large language there are approximately 140 different nodes. In order to make working
|
||||||
with them easier they are grouped into three categories:
|
with them easier they are grouped into three categories:
|
||||||
|
|
||||||
* `PhpParser\Node\Stmt`s are statement nodes, i.e. language constructs that do not return
|
* `PhpParser\Node\Stmt`s are statement nodes, i.e. language constructs that do not return
|
||||||
@ -113,8 +182,9 @@ with them easier they are grouped into three categories:
|
|||||||
* There are some nodes not in either of these groups, for example names (`PhpParser\Node\Name`)
|
* There are some nodes not in either of these groups, for example names (`PhpParser\Node\Name`)
|
||||||
and call arguments (`PhpParser\Node\Arg`).
|
and call arguments (`PhpParser\Node\Arg`).
|
||||||
|
|
||||||
Some node class names have a trailing `_`. This is used whenever the class name would otherwise clash
|
The `Node\Stmt\Expression` node is somewhat confusing in that it contains both the terms "statement"
|
||||||
with a PHP keyword.
|
and "expression". This node distinguishes `expr`, which is a `Node\Expr`, from `expr;`, which is
|
||||||
|
an "expression statement" represented by `Node\Stmt\Expression` and containing `expr` as a sub-node.
|
||||||
|
|
||||||
Every node has a (possibly zero) number of subnodes. You can access subnodes by writing
|
Every node has a (possibly zero) number of subnodes. You can access subnodes by writing
|
||||||
`$node->subNodeName`. The `Stmt\Echo_` node has only one subnode `exprs`. So in order to access it
|
`$node->subNodeName`. The `Stmt\Echo_` node has only one subnode `exprs`. So in order to access it
|
||||||
@ -173,7 +243,7 @@ try {
|
|||||||
|
|
||||||
The above code will output:
|
The above code will output:
|
||||||
|
|
||||||
<?php echo 'Hello ', hi\getTarget();
|
echo 'Hello ', hi\getTarget();
|
||||||
|
|
||||||
As you can see the source code was first parsed using `PhpParser\Parser->parse()`, then changed and then
|
As you can see the source code was first parsed using `PhpParser\Parser->parse()`, then changed and then
|
||||||
again converted to code using `PhpParser\PrettyPrinter\Standard->prettyPrint()`.
|
again converted to code using `PhpParser\PrettyPrinter\Standard->prettyPrint()`.
|
||||||
@ -184,6 +254,8 @@ single expression using `prettyPrintExpr()`.
|
|||||||
The `prettyPrintFile()` method can be used to print an entire file. This will include the opening `<?php` tag
|
The `prettyPrintFile()` method can be used to print an entire file. This will include the opening `<?php` tag
|
||||||
and handle inline HTML as the first/last statement more gracefully.
|
and handle inline HTML as the first/last statement more gracefully.
|
||||||
|
|
||||||
|
> Read more: [Pretty printing documentation](component/Pretty_printing.markdown)
|
||||||
|
|
||||||
Node traversation
|
Node traversation
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
@ -278,10 +350,12 @@ be `array(A, X, Y, Z, C)`.
|
|||||||
Instead of manually implementing the `NodeVisitor` interface you can also extend the `NodeVisitorAbstract`
|
Instead of manually implementing the `NodeVisitor` interface you can also extend the `NodeVisitorAbstract`
|
||||||
class, which will define empty default implementations for all the above methods.
|
class, which will define empty default implementations for all the above methods.
|
||||||
|
|
||||||
|
> Read more: [Walking the AST](component/Walking_the_AST.markdown)
|
||||||
|
|
||||||
The NameResolver node visitor
|
The NameResolver node visitor
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
One visitor is already bundled with the package: `PhpParser\NodeVisitor\NameResolver`. This visitor
|
One visitor that is already bundled with the package is `PhpParser\NodeVisitor\NameResolver`. This visitor
|
||||||
helps you work with namespaced code by trying to resolve most names to fully qualified ones.
|
helps you work with namespaced code by trying to resolve most names to fully qualified ones.
|
||||||
|
|
||||||
For example, consider the following code:
|
For example, consider the following code:
|
||||||
@ -292,7 +366,7 @@ For example, consider the following code:
|
|||||||
In order to know that `B\C` really is `A\C` you would need to track aliases and namespaces yourself.
|
In order to know that `B\C` really is `A\C` you would need to track aliases and namespaces yourself.
|
||||||
The `NameResolver` takes care of that and resolves names as far as possible.
|
The `NameResolver` takes care of that and resolves names as far as possible.
|
||||||
|
|
||||||
After running it most names will be fully qualified. The only names that will stay unqualified are
|
After running it, most names will be fully qualified. The only names that will stay unqualified are
|
||||||
unqualified function and constant names. These are resolved at runtime and thus the visitor can't
|
unqualified function and constant names. These are resolved at runtime and thus the visitor can't
|
||||||
know which function they are referring to. In most cases this is a non-issue as the global functions
|
know which function they are referring to. In most cases this is a non-issue as the global functions
|
||||||
are meant.
|
are meant.
|
||||||
@ -300,6 +374,8 @@ are meant.
|
|||||||
Also the `NameResolver` adds a `namespacedName` subnode to class, function and constant declarations
|
Also the `NameResolver` adds a `namespacedName` subnode to class, function and constant declarations
|
||||||
that contains the namespaced name instead of only the shortname that is available via `name`.
|
that contains the namespaced name instead of only the shortname that is available via `name`.
|
||||||
|
|
||||||
|
> Read more: [Name resolution documentation](component/Name_resolution.markdown)
|
||||||
|
|
||||||
Example: Converting namespaced code to pseudo namespaces
|
Example: Converting namespaced code to pseudo namespaces
|
||||||
--------------------------------------------------------
|
--------------------------------------------------------
|
||||||
|
|
||||||
@ -333,7 +409,7 @@ $files = new \RegexIterator($files, '/\.php$/');
|
|||||||
foreach ($files as $file) {
|
foreach ($files as $file) {
|
||||||
try {
|
try {
|
||||||
// read the file that should be converted
|
// read the file that should be converted
|
||||||
$code = file_get_contents($file);
|
$code = file_get_contents($file->getPathName());
|
||||||
|
|
||||||
// parse
|
// parse
|
||||||
$stmts = $parser->parse($code);
|
$stmts = $parser->parse($code);
|
||||||
|
@ -1,228 +0,0 @@
|
|||||||
Other node tree representations
|
|
||||||
===============================
|
|
||||||
|
|
||||||
It is possible to convert the AST into several textual representations, which serve different uses.
|
|
||||||
|
|
||||||
Simple serialization
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
It is possible to serialize the node tree using `serialize()` and also unserialize it using
|
|
||||||
`unserialize()`. The output is not human readable and not easily processable from anything
|
|
||||||
but PHP, but it is compact and generates quickly. The main application thus is in caching.
|
|
||||||
|
|
||||||
Human readable dumping
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
Furthermore it is possible to dump nodes into a human readable format using the `dump` method of
|
|
||||||
`PhpParser\NodeDumper`. This can be used for debugging.
|
|
||||||
|
|
||||||
```php
|
|
||||||
$code = <<<'CODE'
|
|
||||||
<?php
|
|
||||||
|
|
||||||
function printLine($msg) {
|
|
||||||
echo $msg, "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
printLine('Hello World!!!');
|
|
||||||
CODE;
|
|
||||||
|
|
||||||
$parser = (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::PREFER_PHP7);
|
|
||||||
$nodeDumper = new PhpParser\NodeDumper;
|
|
||||||
|
|
||||||
try {
|
|
||||||
$stmts = $parser->parse($code);
|
|
||||||
|
|
||||||
echo $nodeDumper->dump($stmts), "\n";
|
|
||||||
} catch (PhpParser\Error $e) {
|
|
||||||
echo 'Parse Error: ', $e->getMessage();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The above script will have an output looking roughly like this:
|
|
||||||
|
|
||||||
```
|
|
||||||
array(
|
|
||||||
0: Stmt_Function(
|
|
||||||
byRef: false
|
|
||||||
params: array(
|
|
||||||
0: Param(
|
|
||||||
name: msg
|
|
||||||
default: null
|
|
||||||
type: null
|
|
||||||
byRef: false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
stmts: array(
|
|
||||||
0: Stmt_Echo(
|
|
||||||
exprs: array(
|
|
||||||
0: Expr_Variable(
|
|
||||||
name: msg
|
|
||||||
)
|
|
||||||
1: Scalar_String(
|
|
||||||
value:
|
|
||||||
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
name: printLine
|
|
||||||
)
|
|
||||||
1: Expr_FuncCall(
|
|
||||||
name: Name(
|
|
||||||
parts: array(
|
|
||||||
0: printLine
|
|
||||||
)
|
|
||||||
)
|
|
||||||
args: array(
|
|
||||||
0: Arg(
|
|
||||||
value: Scalar_String(
|
|
||||||
value: Hello World!!!
|
|
||||||
)
|
|
||||||
byRef: false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
JSON encoding
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Nodes (and comments) implement the `JsonSerializable` interface. As such, it is possible to JSON
|
|
||||||
encode the AST directly using `json_encode()`:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$code = <<<'CODE'
|
|
||||||
<?php
|
|
||||||
|
|
||||||
function printLine($msg) {
|
|
||||||
echo $msg, "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
printLine('Hello World!!!');
|
|
||||||
CODE;
|
|
||||||
|
|
||||||
$parser = (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::PREFER_PHP7);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$stmts = $parser->parse($code);
|
|
||||||
|
|
||||||
echo json_encode($stmts, JSON_PRETTY_PRINT), "\n";
|
|
||||||
} catch (PhpParser\Error $e) {
|
|
||||||
echo 'Parse Error: ', $e->getMessage();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This will result in the following output (which includes attributes):
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"nodeType": "Stmt_Function",
|
|
||||||
"byRef": false,
|
|
||||||
"name": "printLine",
|
|
||||||
"params": [
|
|
||||||
{
|
|
||||||
"nodeType": "Param",
|
|
||||||
"type": null,
|
|
||||||
"byRef": false,
|
|
||||||
"variadic": false,
|
|
||||||
"name": "msg",
|
|
||||||
"default": null,
|
|
||||||
"attributes": {
|
|
||||||
"startLine": 3,
|
|
||||||
"endLine": 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"returnType": null,
|
|
||||||
"stmts": [
|
|
||||||
{
|
|
||||||
"nodeType": "Stmt_Echo",
|
|
||||||
"exprs": [
|
|
||||||
{
|
|
||||||
"nodeType": "Expr_Variable",
|
|
||||||
"name": "msg",
|
|
||||||
"attributes": {
|
|
||||||
"startLine": 4,
|
|
||||||
"endLine": 4
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"nodeType": "Scalar_String",
|
|
||||||
"value": "\n",
|
|
||||||
"attributes": {
|
|
||||||
"startLine": 4,
|
|
||||||
"endLine": 4,
|
|
||||||
"kind": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"attributes": {
|
|
||||||
"startLine": 4,
|
|
||||||
"endLine": 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"attributes": {
|
|
||||||
"startLine": 3,
|
|
||||||
"endLine": 5
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"nodeType": "Expr_FuncCall",
|
|
||||||
"name": {
|
|
||||||
"nodeType": "Name",
|
|
||||||
"parts": [
|
|
||||||
"printLine"
|
|
||||||
],
|
|
||||||
"attributes": {
|
|
||||||
"startLine": 7,
|
|
||||||
"endLine": 7
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"args": [
|
|
||||||
{
|
|
||||||
"nodeType": "Arg",
|
|
||||||
"value": {
|
|
||||||
"nodeType": "Scalar_String",
|
|
||||||
"value": "Hello World!!!",
|
|
||||||
"attributes": {
|
|
||||||
"startLine": 7,
|
|
||||||
"endLine": 7,
|
|
||||||
"kind": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"byRef": false,
|
|
||||||
"unpack": false,
|
|
||||||
"attributes": {
|
|
||||||
"startLine": 7,
|
|
||||||
"endLine": 7
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"attributes": {
|
|
||||||
"startLine": 7,
|
|
||||||
"endLine": 7
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
The JSON representation may be converted back into a node tree using the `JsonDecoder`:
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
$nodeDecoder = new PhpParser\NodeDecoder();
|
|
||||||
$ast = $nodeDecoder->decode($json);
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that not all ASTs can be represented using JSON. In particular:
|
|
||||||
|
|
||||||
* JSON only supports UTF-8 strings.
|
|
||||||
* JSON does not support non-finite floating-point numbers. This can occur if the original source
|
|
||||||
code contains non-representable floating-pointing literals such as `1e1000`.
|
|
||||||
|
|
||||||
If the node tree is not representable in JSON, the initial `json_encode()` call will fail.
|
|
@ -6,11 +6,17 @@ Guide
|
|||||||
|
|
||||||
1. [Introduction](0_Introduction.markdown)
|
1. [Introduction](0_Introduction.markdown)
|
||||||
2. [Usage of basic components](2_Usage_of_basic_components.markdown)
|
2. [Usage of basic components](2_Usage_of_basic_components.markdown)
|
||||||
3. [Other node tree representations](3_Other_node_tree_representations.markdown)
|
|
||||||
|
|
||||||
Component documentation
|
Component documentation
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
|
* [Walking the AST](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](component/Name_resolution.markdown)
|
* [Name resolution](component/Name_resolution.markdown)
|
||||||
* Name resolver options
|
* Name resolver options
|
||||||
* Name resolution context
|
* Name resolution context
|
||||||
@ -30,6 +36,8 @@ Component documentation
|
|||||||
* [Constant expression evaluation](component/Constant_expression_evaluation.markdown)
|
* [Constant expression evaluation](component/Constant_expression_evaluation.markdown)
|
||||||
* Evaluating constant/property/etc initializers
|
* Evaluating constant/property/etc initializers
|
||||||
* Handling errors and unsupported expressions
|
* Handling errors and unsupported expressions
|
||||||
|
* [JSON representation](component/JSON_representation.markdown)
|
||||||
|
* JSON encoding and decoding of ASTs
|
||||||
* [Performance](component/Performance.markdown)
|
* [Performance](component/Performance.markdown)
|
||||||
* Disabling XDebug
|
* Disabling XDebug
|
||||||
* Reusing objects
|
* Reusing objects
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
AST builders
|
AST builders
|
||||||
============
|
============
|
||||||
|
|
||||||
When PHP-Parser is used to generate (or modify) code, by first creating an Abstract Syntax Tree and
|
When PHP-Parser is used to generate (or modify) code by first creating an Abstract Syntax Tree and
|
||||||
then using the [pretty printer](Pretty_printing.markdown) to convert it to PHP code, it can often
|
then using the [pretty printer](Pretty_printing.markdown) to convert it to PHP code, it can often
|
||||||
be tedious to manually construct AST nodes. The project provides a number of utilities to simplify
|
be tedious to manually construct AST nodes. The project provides a number of utilities to simplify
|
||||||
the construction of common AST nodes.
|
the construction of common AST nodes.
|
||||||
@ -28,16 +28,26 @@ use PhpParser\Node;
|
|||||||
|
|
||||||
$factory = new BuilderFactory;
|
$factory = new BuilderFactory;
|
||||||
$node = $factory->namespace('Name\Space')
|
$node = $factory->namespace('Name\Space')
|
||||||
->addStmt($factory->use('Some\Other\Thingy')->as('SomeOtherClass'))
|
->addStmt($factory->use('Some\Other\Thingy')->as('SomeClass'))
|
||||||
|
->addStmt($factory->useFunction('strlen'))
|
||||||
|
->addStmt($factory->useConst('PHP_VERSION'))
|
||||||
->addStmt($factory->class('SomeOtherClass')
|
->addStmt($factory->class('SomeOtherClass')
|
||||||
->extend('SomeClass')
|
->extend('SomeClass')
|
||||||
->implement('A\Few', '\Interfaces')
|
->implement('A\Few', '\Interfaces')
|
||||||
->makeAbstract() // ->makeFinal()
|
->makeAbstract() // ->makeFinal()
|
||||||
|
|
||||||
|
->addStmt($factory->useTrait('FirstTrait'))
|
||||||
|
|
||||||
|
->addStmt($factory->useTrait('SecondTrait', 'ThirdTrait')
|
||||||
|
->and('AnotherTrait')
|
||||||
|
->with($factory->traitUseAdaptation('foo')->as('bar'))
|
||||||
|
->with($factory->traitUseAdaptation('AnotherTrait', 'baz')->as('test'))
|
||||||
|
->with($factory->traitUseAdaptation('AnotherTrait', 'func')->insteadof('SecondTrait')))
|
||||||
|
|
||||||
->addStmt($factory->method('someMethod')
|
->addStmt($factory->method('someMethod')
|
||||||
->makePublic()
|
->makePublic()
|
||||||
->makeAbstract() // ->makeFinal()
|
->makeAbstract() // ->makeFinal()
|
||||||
->setReturnType('bool')
|
->setReturnType('bool') // ->makeReturnByRef()
|
||||||
->addParam($factory->param('someParam')->setTypeHint('SomeClass'))
|
->addParam($factory->param('someParam')->setTypeHint('SomeClass'))
|
||||||
->setDocComment('/**
|
->setDocComment('/**
|
||||||
* This method does something.
|
* This method does something.
|
||||||
@ -74,8 +84,16 @@ This will produce the following output with the standard pretty printer:
|
|||||||
namespace Name\Space;
|
namespace Name\Space;
|
||||||
|
|
||||||
use Some\Other\Thingy as SomeClass;
|
use Some\Other\Thingy as SomeClass;
|
||||||
|
use function strlen;
|
||||||
|
use const PHP_VERSION;
|
||||||
abstract class SomeOtherClass extends SomeClass implements A\Few, \Interfaces
|
abstract class SomeOtherClass extends SomeClass implements A\Few, \Interfaces
|
||||||
{
|
{
|
||||||
|
use FirstTrait;
|
||||||
|
use SecondTrait, ThirdTrait, AnotherTrait {
|
||||||
|
foo as bar;
|
||||||
|
AnotherTrait::baz as test;
|
||||||
|
AnotherTrait::func insteadof SecondTrait;
|
||||||
|
}
|
||||||
protected $someProperty;
|
protected $someProperty;
|
||||||
private $anotherProperty = array(1, 2, 3);
|
private $anotherProperty = array(1, 2, 3);
|
||||||
/**
|
/**
|
||||||
@ -98,9 +116,23 @@ The `BuilderFactory` also provides a number of additional helper methods, which
|
|||||||
nodes. The following methods are currently available:
|
nodes. The following methods are currently available:
|
||||||
|
|
||||||
* `val($value)`: Creates an AST node for a literal value like `42` or `[1, 2, 3]`.
|
* `val($value)`: Creates an AST node for a literal value like `42` or `[1, 2, 3]`.
|
||||||
|
* `var($name)`: Creates variable node.
|
||||||
* `args(array $args)`: Creates an array of function/method arguments, including the required `Arg`
|
* `args(array $args)`: Creates an array of function/method arguments, including the required `Arg`
|
||||||
wrappers. Also converts literals to AST nodes.
|
wrappers. Also converts literals to AST nodes.
|
||||||
|
* `funcCall($name, array $args = [])`: Create a function call node. Converts `$name` to a `Name`
|
||||||
|
node and normalizes arguments.
|
||||||
|
* `methodCall(Expr $var, $name, array $args = [])`: Create a method call node. Converts `$name` to
|
||||||
|
an `Identifier` node and normalizes arguments.
|
||||||
|
* `staticCall($class, $name, array $args = [])`: Create a static method call node. Converts
|
||||||
|
`$class` to a `Name` node, `$name` to an `Identifier` node and normalizes arguments.
|
||||||
|
* `new($class, array $args = [])`: Create a "new" (object creation) node. Converts `$class` to a
|
||||||
|
`Name` node.
|
||||||
|
* `constFetch($name)`: Create a constant fetch node. Converts `$name` to a `Name` node.
|
||||||
|
* `classConstFetch($class, $name)`: Create a class constant fetch node. Converts `$class` to a
|
||||||
|
`Name` node and `$name` to an `Identifier` node.
|
||||||
|
* `propertyFetch($var, $name)`: Creates a property fetch node. Converts `$name` to an `Identifier`
|
||||||
|
node.
|
||||||
* `concat(...$exprs)`: Create a tree of `BinaryOp\Concat` nodes for the given expressions.
|
* `concat(...$exprs)`: Create a tree of `BinaryOp\Concat` nodes for the given expressions.
|
||||||
|
|
||||||
These methods may be expanded on an as-needed basis. Please open an issue or PR if a common
|
These methods may be expanded on an as-needed basis. Please open an issue or PR if a common
|
||||||
operation is missing.
|
operation is missing.
|
||||||
|
@ -27,7 +27,7 @@ try {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Before using column information its availability needs to be checked with `$e->hasColumnInfo()`, as the precise
|
Before using column information, its availability needs to be checked with `$e->hasColumnInfo()`, as the precise
|
||||||
location of an error cannot always be determined. The methods for retrieving column information also have to be passed
|
location of an error cannot always be determined. The methods for retrieving column information also have to be passed
|
||||||
the source code of the parsed file. An example for printing an error:
|
the source code of the parsed file. An example for printing an error:
|
||||||
|
|
||||||
|
131
doc/component/JSON_representation.markdown
Normal file
131
doc/component/JSON_representation.markdown
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
JSON representation
|
||||||
|
===================
|
||||||
|
|
||||||
|
Nodes (and comments) implement the `JsonSerializable` interface. As such, it is possible to JSON
|
||||||
|
encode the AST directly using `json_encode()`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use PhpParser\ParserFactory;
|
||||||
|
|
||||||
|
$code = <<<'CODE'
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/** @param string $msg */
|
||||||
|
function printLine($msg) {
|
||||||
|
echo $msg, "\n";
|
||||||
|
}
|
||||||
|
CODE;
|
||||||
|
|
||||||
|
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmts = $parser->parse($code);
|
||||||
|
|
||||||
|
echo json_encode($stmts, JSON_PRETTY_PRINT), "\n";
|
||||||
|
} catch (PhpParser\Error $e) {
|
||||||
|
echo 'Parse Error: ', $e->getMessage();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This will result in the following output (which includes attributes):
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"nodeType": "Stmt_Function",
|
||||||
|
"byRef": false,
|
||||||
|
"name": {
|
||||||
|
"nodeType": "Identifier",
|
||||||
|
"name": "printLine",
|
||||||
|
"attributes": {
|
||||||
|
"startLine": 4,
|
||||||
|
"endLine": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"nodeType": "Param",
|
||||||
|
"type": null,
|
||||||
|
"byRef": false,
|
||||||
|
"variadic": false,
|
||||||
|
"var": {
|
||||||
|
"nodeType": "Expr_Variable",
|
||||||
|
"name": "msg",
|
||||||
|
"attributes": {
|
||||||
|
"startLine": 4,
|
||||||
|
"endLine": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": null,
|
||||||
|
"attributes": {
|
||||||
|
"startLine": 4,
|
||||||
|
"endLine": 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returnType": null,
|
||||||
|
"stmts": [
|
||||||
|
{
|
||||||
|
"nodeType": "Stmt_Echo",
|
||||||
|
"exprs": [
|
||||||
|
{
|
||||||
|
"nodeType": "Expr_Variable",
|
||||||
|
"name": "msg",
|
||||||
|
"attributes": {
|
||||||
|
"startLine": 5,
|
||||||
|
"endLine": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nodeType": "Scalar_String",
|
||||||
|
"value": "\n",
|
||||||
|
"attributes": {
|
||||||
|
"startLine": 5,
|
||||||
|
"endLine": 5,
|
||||||
|
"kind": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"attributes": {
|
||||||
|
"startLine": 5,
|
||||||
|
"endLine": 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"attributes": {
|
||||||
|
"startLine": 4,
|
||||||
|
"comments": [
|
||||||
|
{
|
||||||
|
"nodeType": "Comment_Doc",
|
||||||
|
"text": "\/** @param string $msg *\/",
|
||||||
|
"line": 3,
|
||||||
|
"filePos": 9,
|
||||||
|
"tokenPos": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"endLine": 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
The JSON representation may be converted back into an AST using the `JsonDecoder`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$nodeDecoder = new PhpParser\NodeDecoder();
|
||||||
|
$ast = $nodeDecoder->decode($json);
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that not all ASTs can be represented using JSON. In particular:
|
||||||
|
|
||||||
|
* JSON only supports UTF-8 strings.
|
||||||
|
* JSON does not support non-finite floating-point numbers. This can occur if the original source
|
||||||
|
code contains non-representable floating-pointing literals such as `1e1000`.
|
||||||
|
|
||||||
|
If the node tree is not representable in JSON, the initial `json_encode()` call will fail.
|
||||||
|
|
||||||
|
From the command line, a JSON dump can be obtained using `vendor/bin/php-parse -j file.php`.
|
@ -107,9 +107,9 @@ function handleHaltCompiler(): string;
|
|||||||
function getNextToken(string &$value = null, array &$startAttributes = null, array &$endAttributes = null): int;
|
function getNextToken(string &$value = null, array &$startAttributes = null, array &$endAttributes = null): int;
|
||||||
```
|
```
|
||||||
|
|
||||||
The `startLexing()` method is invoked with the source code that is to be lexed (including the opening tag) whenever the
|
The `startLexing()` method is invoked whenever the `parse()` method of the parser is called and is passed the source
|
||||||
`parse()` method of the parser is called. It can be used to reset state or preprocess the source code or tokens. The
|
code that is to be lexed (including the opening tag). It can be used to reset state or preprocess the source code or tokens. The
|
||||||
passes `ErrorHandler` should be used to report lexing errors.
|
passed `ErrorHandler` should be used to report lexing errors.
|
||||||
|
|
||||||
The `getTokens()` method returns the current token array, in the usual `token_get_all()` format. This method is not
|
The `getTokens()` method returns the current token array, in the usual `token_get_all()` format. This method is not
|
||||||
used by the parser (which uses `getNextToken()`), but is useful in combination with the token position attributes.
|
used by the parser (which uses `getNextToken()`), but is useful in combination with the token position attributes.
|
||||||
|
@ -17,9 +17,9 @@ the `xdebug.default_enable=0` ini option does *not* disable XDebug. The *only* w
|
|||||||
XDebug is to not load the extension in the first place.
|
XDebug is to not load the extension in the first place.
|
||||||
|
|
||||||
If you are building a command-line utility for use by developers (who often have XDebug enabled),
|
If you are building a command-line utility for use by developers (who often have XDebug enabled),
|
||||||
you may want to consider automatically restarting PHP with XDebug unloaded. See the composer
|
you may want to consider automatically restarting PHP with XDebug unloaded. The
|
||||||
[XdebugHandler](https://github.com/composer/composer/blob/master/src/Composer/XdebugHandler.php)
|
[composer/xdebug-handler](https://github.com/composer/xdebug-handler) package can be used to do
|
||||||
for an implementation of such functionality.
|
this.
|
||||||
|
|
||||||
If you do run with XDebug, you may need to increase the `xdebug.max_nesting_level` option to a
|
If you do run with XDebug, you may need to increase the `xdebug.max_nesting_level` option to a
|
||||||
higher level, such as 3000. While the parser itself is recursion free, most other code working on
|
higher level, such as 3000. While the parser itself is recursion free, most other code working on
|
||||||
|
@ -52,8 +52,8 @@ For automated code refactoring, migration and similar, you will usually only wan
|
|||||||
portion of the code and leave the remainder alone. The basic pretty printer is not suitable for
|
portion of the code and leave the remainder alone. The basic pretty printer is not suitable for
|
||||||
this, because it will also reformat parts of the code which have not been modified.
|
this, because it will also reformat parts of the code which have not been modified.
|
||||||
|
|
||||||
Since PHP-Parser 4.0 an experimental formatting-preserving pretty-printing mode is available, which
|
Since PHP-Parser 4.0, an experimental formatting-preserving pretty-printing mode is available, which
|
||||||
attempts to preserve the formatting of code, those AST nodes have not changed, and only reformat
|
attempts to preserve the formatting of code (those AST nodes that have not changed) and only reformat
|
||||||
code which has been modified or newly inserted.
|
code which has been modified or newly inserted.
|
||||||
|
|
||||||
Use of the formatting-preservation functionality requires some additional preparatory steps:
|
Use of the formatting-preservation functionality requires some additional preparatory steps:
|
||||||
@ -86,7 +86,7 @@ $newCode = $printer->printFormatPreserving($newStmts, $oldStmts, $oldTokens);
|
|||||||
```
|
```
|
||||||
|
|
||||||
If you make use of the name resolution functionality, you will likely want to disable the
|
If you make use of the name resolution functionality, you will likely want to disable the
|
||||||
`replaceNames` option. This will add resolved names as attributes, instead of directlying modifying
|
`replaceNodes` option. This will add resolved names as attributes, instead of directlying modifying
|
||||||
the AST and causing spurious changes to the pretty printed code. For more information, see the
|
the AST and causing spurious changes to the pretty printed code. For more information, see the
|
||||||
[name resolution documentation](Name_resolution.markdown).
|
[name resolution documentation](Name_resolution.markdown).
|
||||||
|
|
||||||
|
335
doc/component/Walking_the_AST.markdown
Normal file
335
doc/component/Walking_the_AST.markdown
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
Walking the AST
|
||||||
|
===============
|
||||||
|
|
||||||
|
The most common way to work with the AST is by using a node traverser and one or more node visitors.
|
||||||
|
As a basic example, the following code changes all literal integers in the AST into strings (e.g.,
|
||||||
|
`42` becomes `'42'`.)
|
||||||
|
|
||||||
|
```php
|
||||||
|
use PhpParser\{Node, NodeTraverser, NodeVisitorAbstract};
|
||||||
|
|
||||||
|
$traverser = new NodeTraverser;
|
||||||
|
$traverser->addVisitor(new class extends NodeVisitorAbstract {
|
||||||
|
public function leaveNode(Node $node) {
|
||||||
|
if ($node instanceof Node\Scalar\LNumber) {
|
||||||
|
return new Node\Scalar\String_((string) $node->value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$stmts = ...;
|
||||||
|
$modifiedStmts = $traverser->traverse($stmts);
|
||||||
|
```
|
||||||
|
|
||||||
|
Node visitors
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Each node visitor implements an interface with following four methods:
|
||||||
|
|
||||||
|
```php
|
||||||
|
interface NodeVisitor {
|
||||||
|
public function beforeTraverse(array $nodes);
|
||||||
|
public function enterNode(Node $node);
|
||||||
|
public function leaveNode(Node $node);
|
||||||
|
public function afterTraverse(array $nodes);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `beforeTraverse()` and `afterTraverse()` methods are called before and after the traversal
|
||||||
|
respectively, and are passed the entire AST. They can be used to perform any necessary state
|
||||||
|
setup or cleanup.
|
||||||
|
|
||||||
|
The `enterNode()` method is called when a node is first encountered, before its children are
|
||||||
|
processed ("preorder"). The `leaveNode()` method is called after all children have been visited
|
||||||
|
("postorder").
|
||||||
|
|
||||||
|
For example, if we have the following excerpt of an AST
|
||||||
|
|
||||||
|
```
|
||||||
|
Expr_FuncCall(
|
||||||
|
name: Name(
|
||||||
|
parts: array(
|
||||||
|
0: printLine
|
||||||
|
)
|
||||||
|
)
|
||||||
|
args: array(
|
||||||
|
0: Arg(
|
||||||
|
value: Scalar_String(
|
||||||
|
value: Hello World!!!
|
||||||
|
)
|
||||||
|
byRef: false
|
||||||
|
unpack: false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
then the enter/leave methods will be called in the following order:
|
||||||
|
|
||||||
|
```
|
||||||
|
enterNode(Expr_FuncCall)
|
||||||
|
enterNode(Name)
|
||||||
|
leaveNode(Name)
|
||||||
|
enterNode(Arg)
|
||||||
|
enterNode(Scalar_String)
|
||||||
|
leaveNode(Scalar_String)
|
||||||
|
leaveNode(Arg)
|
||||||
|
leaveNode(Expr_FuncCall)
|
||||||
|
```
|
||||||
|
|
||||||
|
A common pattern is that `enterNode` is used to collect some information and then `leaveNode`
|
||||||
|
performs modifications based on that. At the time when `leaveNode` is called, all the code inside
|
||||||
|
the node will have already been visited and necessary information collected.
|
||||||
|
|
||||||
|
As you usually do not want to implement all four methods, it is recommended that you extend
|
||||||
|
`NodeVisitorAbstract` instead of implementing the interface directly. The abstract class provides
|
||||||
|
empty default implementations.
|
||||||
|
|
||||||
|
Modifying the AST
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
There are a number of ways in which the AST can be modified from inside a node visitor. The first
|
||||||
|
and simplest is to simply change AST properties inside the visitor:
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function leaveNode(Node $node) {
|
||||||
|
if ($node instanceof Node\Scalar\LNumber) {
|
||||||
|
// increment all integer literals
|
||||||
|
$node->value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The second is to replace a node entirely by returning a new node:
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function leaveNode(Node $node) {
|
||||||
|
if ($node instanceof Node\Expr\BinaryOp\BooleanAnd) {
|
||||||
|
// Convert all $a && $b expressions into !($a && $b)
|
||||||
|
return new Node\Expr\BooleanNot($node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Doing this is supported both inside enterNode and leaveNode. However, you have to be mindful about
|
||||||
|
where you perform the replacement: If a node is replaced in enterNode, then the recursive traversal
|
||||||
|
will also consider the children of the new node. If you aren't careful, this can lead to infinite
|
||||||
|
recursion. For example, let's take the previous code sample and use enterNode instead:
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function enterNode(Node $node) {
|
||||||
|
if ($node instanceof Node\Expr\BinaryOp\BooleanAnd) {
|
||||||
|
// Convert all $a && $b expressions into !($a && $b)
|
||||||
|
return new Node\Expr\BooleanNot($node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now `$a && $b` will be replaced by `!($a && $b)`. Then the traverser will go into the first (and
|
||||||
|
only) child of `!($a && $b)`, which is `$a && $b`. The transformation applies again and we end up
|
||||||
|
with `!!($a && $b)`. This will continue until PHP hits the memory limit.
|
||||||
|
|
||||||
|
Finally, two special replacement types are supported only by leaveNode. The first is removal of a
|
||||||
|
node:
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function leaveNode(Node $node) {
|
||||||
|
if ($node instanceof Node\Stmt\Return_) {
|
||||||
|
// Remove all return statements
|
||||||
|
return NodeTraverser::REMOVE_NODE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Node removal only works if the parent structure is an array. This means that usually it only makes
|
||||||
|
sense to remove nodes of type `Node\Stmt`, as they always occur inside statement lists (and a few
|
||||||
|
more node types like `Arg` or `Expr\ArrayItem`, which are also always part of lists).
|
||||||
|
|
||||||
|
On the other hand, removing a `Node\Expr` does not make sense: If you have `$a * $b`, there is no
|
||||||
|
meaningful way in which the `$a` part could be removed. If you want to remove an expression, you
|
||||||
|
generally want to remove it together with a surrounding expression statement:
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function leaveNode(Node $node) {
|
||||||
|
if ($node instanceof Node\Stmt\Expression
|
||||||
|
&& $node->expr instanceof Node\Expr\FuncCall
|
||||||
|
&& $node->expr->name instanceof Node\Name
|
||||||
|
&& $node->expr->name->toString() === 'var_dump'
|
||||||
|
) {
|
||||||
|
return NodeTraverser::REMOVE_NODE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This example will remove all calls to `var_dump()` which occur as expression statements. This means
|
||||||
|
that `var_dump($a);` will be removed, but `if (var_dump($a))` will not be removed (and there is no
|
||||||
|
obvious way in which it can be removed).
|
||||||
|
|
||||||
|
Next to removing nodes, it is also possible to replace one node with multiple nodes. Again, this
|
||||||
|
only works inside leaveNode and only if the parent structure is an array.
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function leaveNode(Node $node) {
|
||||||
|
if ($node instanceof Node\Stmt\Return_ && $node->expr !== null) {
|
||||||
|
// Convert "return foo();" into "$retval = foo(); return $retval;"
|
||||||
|
$var = new Node\Expr\Variable('retval');
|
||||||
|
return [
|
||||||
|
new Node\Stmt\Expression(new Node\Expr\Assign($var, $node->expr)),
|
||||||
|
new Node\Stmt\Return_($var),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Short-circuiting traversal
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
An AST can easily contain thousands of nodes, and traversing over all of them may be slow,
|
||||||
|
especially if you have more than one visitor. In some cases, it is possible to avoid a full
|
||||||
|
traversal.
|
||||||
|
|
||||||
|
If you are looking for all class declarations in a file (and assuming you're not interested in
|
||||||
|
anonymous classes), you know that once you've seen a class declaration, there is no point in also
|
||||||
|
checking all it's child nodes, because PHP does not allow nesting classes. In this case, you can
|
||||||
|
instruct the traverser to not recurse into the class node:
|
||||||
|
|
||||||
|
```
|
||||||
|
private $classes = [];
|
||||||
|
public function enterNode(Node $node) {
|
||||||
|
if ($node instanceof Node\Stmt\Class_) {
|
||||||
|
$this->classes[] = $node;
|
||||||
|
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Of course, this option is only available in enterNode, because it's already too late by the time
|
||||||
|
leaveNode is reached.
|
||||||
|
|
||||||
|
If you are only looking for one specific node, it is also possible to abort the traversal entirely
|
||||||
|
after finding it. For example, if you are looking for the node of a class with a certain name (and
|
||||||
|
discounting exotic cases like conditionally defining a class two times), you can stop traversal
|
||||||
|
once you found it:
|
||||||
|
|
||||||
|
```
|
||||||
|
private $class = null;
|
||||||
|
public function enterNode(Node $node) {
|
||||||
|
if ($node instanceof Node\Stmt\Class_ &&
|
||||||
|
$node->namespaceName->toString() === 'Foo\Bar\Baz'
|
||||||
|
) {
|
||||||
|
$this->class = $node;
|
||||||
|
return NodeTraverser::STOP_TRAVERSAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This works both in enterNode and leaveNode. Note that this particular case can also be more easily
|
||||||
|
handled using a NodeFinder, which will be introduced below.
|
||||||
|
|
||||||
|
Multiple visitors
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
A single traverser can be used with multiple visitors:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$traverser = new NodeTraverser;
|
||||||
|
$traverser->addVisitor($visitorA);
|
||||||
|
$traverser->addVisitor($visitorB);
|
||||||
|
$stmts = $traverser->traverser($stmts);
|
||||||
|
```
|
||||||
|
|
||||||
|
It is important to understand that if a traverser is run with multiple visitors, the visitors will
|
||||||
|
be interleaved. Given the following AST excerpt
|
||||||
|
|
||||||
|
```
|
||||||
|
Stmt_Return(
|
||||||
|
expr: Expr_Variable(
|
||||||
|
name: foobar
|
||||||
|
)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
the following method calls will be performed:
|
||||||
|
|
||||||
|
```
|
||||||
|
$visitorA->enterNode(Stmt_Return)
|
||||||
|
$visitorB->enterNode(Stmt_Return)
|
||||||
|
$visitorA->enterNode(Expr_Variable)
|
||||||
|
$visitorB->enterNode(Expr_Variable)
|
||||||
|
$visitorA->leaveNode(Expr_Variable)
|
||||||
|
$visitorB->leaveNode(Expr_Variable)
|
||||||
|
$visitorA->leaveNode(Stmt_Return)
|
||||||
|
$visitorB->leaveNode(Stmt_Return)
|
||||||
|
```
|
||||||
|
|
||||||
|
That is, when visiting a node, enterNode and leaveNode will always be called for all visitors.
|
||||||
|
Running multiple visitors in parallel improves performance, as the AST only has to be traversed
|
||||||
|
once. However, it is not always possible to write visitors in a way that allows interleaved
|
||||||
|
execution. In this case, you can always fall back to performing multiple traversals:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$traverserA = new NodeTraverser;
|
||||||
|
$traverserA->addVisitor($visitorA);
|
||||||
|
$traverserB = new NodeTraverser;
|
||||||
|
$traverserB->addVisitor($visitorB);
|
||||||
|
$stmts = $traverserA->traverser($stmts);
|
||||||
|
$stmts = $traverserB->traverser($stmts);
|
||||||
|
```
|
||||||
|
|
||||||
|
When using multiple visitors, it is important to understand how they interact with the various
|
||||||
|
special enterNode/leaveNode return values:
|
||||||
|
|
||||||
|
* If *any* visitor returns `DONT_TRAVERSE_CHILDREN`, the children will be skipped for *all*
|
||||||
|
visitors.
|
||||||
|
* If *any* visitor returns `STOP_TRAVERSAL`, traversal is stopped for *all* visitors.
|
||||||
|
* If a visitor returns a replacement node, subsequent visitors will be passed the replacement node,
|
||||||
|
not the original one.
|
||||||
|
* If a visitor returns `REMOVE_NODE`, subsequent visitors will not see this node.
|
||||||
|
* If a visitor returns an array of replacement nodes, subsequent visitors will see neither the node
|
||||||
|
that was replaced, nor the replacement nodes.
|
||||||
|
|
||||||
|
Simple node finding
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
While the node visitor mechanism is very flexible, creating a node visitor can be overly cumbersome
|
||||||
|
for minor tasks. For this reason a `NodeFinder` is provided, which can find AST nodes that either
|
||||||
|
satisfy a certain callback, or which are instanced of a certain node type. A couple of examples are
|
||||||
|
shown in the following:
|
||||||
|
|
||||||
|
```php
|
||||||
|
use PhpParser\{Node, NodeFinder};
|
||||||
|
|
||||||
|
$nodeFinder = new NodeFinder;
|
||||||
|
|
||||||
|
// Find all class nodes.
|
||||||
|
$classes = $nodeFinder->findInstanceOf($stmts, Node\Stmt\Class_::class);
|
||||||
|
|
||||||
|
// Find all classes that extend another class
|
||||||
|
$extendingClasses = $nodeFinder->findInstanceOf($stmts, function(Node $node) {
|
||||||
|
return $node instanceof Node\Stmt\Class_
|
||||||
|
&& $node->extends !== null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find first class occuring in the AST. Returns null if no class exists.
|
||||||
|
$class = $nodeFinder->findFirstInstanceOf($stmts, Node\Stmt\Class_::class);
|
||||||
|
|
||||||
|
// Find first class that has name $name
|
||||||
|
$class = $nodeFinder->findFirst($stmts, function(Node $node) use ($name) {
|
||||||
|
return $node instanceof Node\Stmt\Class_
|
||||||
|
&& $node->resolvedName->toString() === $name;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Internally, the `NodeFinder` also uses a node traverser. It only simplifies the interface for a
|
||||||
|
common use case.
|
||||||
|
|
||||||
|
Parent and sibling references
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
The node visitor mechanism is somewhat rigid, in that it prescribes an order in which nodes should
|
||||||
|
be accessed: From parents to children. However, it can often be convenient to operate in the
|
||||||
|
reverse direction: When working on a node, you might want to check if the parent node satisfies a
|
||||||
|
certain property.
|
||||||
|
|
||||||
|
PHP-Parser does not add parent (or sibling) references to nodes by itself, but you can easily
|
||||||
|
emulate this with a visitor. See the [FAQ](FAQ.markdown) for more information.
|
@ -335,8 +335,8 @@ case_list:
|
|||||||
;
|
;
|
||||||
|
|
||||||
case:
|
case:
|
||||||
T_CASE expr case_separator inner_statement_list { $$ = Stmt\Case_[$2, $4]; }
|
T_CASE expr case_separator inner_statement_list_ex { $$ = Stmt\Case_[$2, $4]; }
|
||||||
| T_DEFAULT case_separator inner_statement_list { $$ = Stmt\Case_[null, $3]; }
|
| T_DEFAULT case_separator inner_statement_list_ex { $$ = Stmt\Case_[null, $3]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
case_separator:
|
case_separator:
|
||||||
@ -454,11 +454,17 @@ static_var:
|
|||||||
| plain_variable '=' static_scalar { $$ = Stmt\StaticVar[$1, $3]; }
|
| plain_variable '=' static_scalar { $$ = Stmt\StaticVar[$1, $3]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
class_statement_list:
|
class_statement_list_ex:
|
||||||
class_statement_list class_statement { push($1, $2); }
|
class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } }
|
||||||
| /* empty */ { init(); }
|
| /* empty */ { init(); }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
class_statement_list:
|
||||||
|
class_statement_list_ex
|
||||||
|
{ makeNop($nop, $this->lookaheadStartAttributes, $this->endAttributes);
|
||||||
|
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
||||||
|
;
|
||||||
|
|
||||||
class_statement:
|
class_statement:
|
||||||
variable_modifiers property_declaration_list ';'
|
variable_modifiers property_declaration_list ';'
|
||||||
{ $$ = Stmt\Property[$1, $2]; $this->checkProperty($$, #1); }
|
{ $$ = Stmt\Property[$1, $2]; $this->checkProperty($$, #1); }
|
||||||
|
@ -240,6 +240,8 @@ non_empty_statement:
|
|||||||
{ $$ = Stmt\Foreach_[$3, $5[0], ['keyVar' => null, 'byRef' => $5[1], 'stmts' => $7]]; }
|
{ $$ = Stmt\Foreach_[$3, $5[0], ['keyVar' => null, 'byRef' => $5[1], 'stmts' => $7]]; }
|
||||||
| T_FOREACH '(' expr T_AS variable T_DOUBLE_ARROW foreach_variable ')' foreach_statement
|
| T_FOREACH '(' expr T_AS variable T_DOUBLE_ARROW foreach_variable ')' foreach_statement
|
||||||
{ $$ = Stmt\Foreach_[$3, $7[0], ['keyVar' => $5, 'byRef' => $7[1], 'stmts' => $9]]; }
|
{ $$ = Stmt\Foreach_[$3, $7[0], ['keyVar' => $5, 'byRef' => $7[1], 'stmts' => $9]]; }
|
||||||
|
| T_FOREACH '(' expr error ')' foreach_statement
|
||||||
|
{ $$ = Stmt\Foreach_[$3, new Expr\Error(stackAttributes(#4)), ['stmts' => $6]]; }
|
||||||
| T_DECLARE '(' declare_list ')' declare_statement { $$ = Stmt\Declare_[$3, $5]; }
|
| T_DECLARE '(' declare_list ')' declare_statement { $$ = Stmt\Declare_[$3, $5]; }
|
||||||
| T_TRY '{' inner_statement_list '}' catches optional_finally
|
| T_TRY '{' inner_statement_list '}' catches optional_finally
|
||||||
{ $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); }
|
{ $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); }
|
||||||
@ -277,7 +279,7 @@ optional_finally:
|
|||||||
;
|
;
|
||||||
|
|
||||||
variables_list:
|
variables_list:
|
||||||
non_empty_variables_list no_comma { $$ = $1; }
|
non_empty_variables_list optional_comma { $$ = $1; }
|
||||||
;
|
;
|
||||||
|
|
||||||
non_empty_variables_list:
|
non_empty_variables_list:
|
||||||
@ -295,9 +297,14 @@ optional_ellipsis:
|
|||||||
| T_ELLIPSIS { $$ = true; }
|
| T_ELLIPSIS { $$ = true; }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
block_or_error:
|
||||||
|
'{' inner_statement_list '}' { $$ = $2; }
|
||||||
|
| error { $$ = []; }
|
||||||
|
;
|
||||||
|
|
||||||
function_declaration_statement:
|
function_declaration_statement:
|
||||||
T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
|
T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type block_or_error
|
||||||
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $9]]; }
|
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $8]]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
class_declaration_statement:
|
class_declaration_statement:
|
||||||
@ -383,8 +390,8 @@ case_list:
|
|||||||
;
|
;
|
||||||
|
|
||||||
case:
|
case:
|
||||||
T_CASE expr case_separator inner_statement_list { $$ = Stmt\Case_[$2, $4]; }
|
T_CASE expr case_separator inner_statement_list_ex { $$ = Stmt\Case_[$2, $4]; }
|
||||||
| T_DEFAULT case_separator inner_statement_list { $$ = Stmt\Case_[null, $3]; }
|
| T_DEFAULT case_separator inner_statement_list_ex { $$ = Stmt\Case_[null, $3]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
case_separator:
|
case_separator:
|
||||||
@ -447,6 +454,8 @@ parameter:
|
|||||||
{ $$ = Node\Param[$4, null, $1, $2, $3]; $this->checkParam($$); }
|
{ $$ = Node\Param[$4, null, $1, $2, $3]; $this->checkParam($$); }
|
||||||
| optional_param_type optional_ref optional_ellipsis plain_variable '=' expr
|
| optional_param_type optional_ref optional_ellipsis plain_variable '=' expr
|
||||||
{ $$ = Node\Param[$4, $6, $1, $2, $3]; $this->checkParam($$); }
|
{ $$ = Node\Param[$4, $6, $1, $2, $3]; $this->checkParam($$); }
|
||||||
|
| optional_param_type optional_ref optional_ellipsis error
|
||||||
|
{ $$ = Node\Param[Expr\Error[], null, $1, $2, $3]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
type_expr:
|
type_expr:
|
||||||
@ -472,7 +481,7 @@ optional_return_type:
|
|||||||
|
|
||||||
argument_list:
|
argument_list:
|
||||||
'(' ')' { $$ = array(); }
|
'(' ')' { $$ = array(); }
|
||||||
| '(' non_empty_argument_list no_comma ')' { $$ = $2; }
|
| '(' non_empty_argument_list optional_comma ')' { $$ = $2; }
|
||||||
;
|
;
|
||||||
|
|
||||||
non_empty_argument_list:
|
non_empty_argument_list:
|
||||||
@ -513,11 +522,17 @@ static_var:
|
|||||||
| plain_variable '=' expr { $$ = Stmt\StaticVar[$1, $3]; }
|
| plain_variable '=' expr { $$ = Stmt\StaticVar[$1, $3]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
class_statement_list:
|
class_statement_list_ex:
|
||||||
class_statement_list class_statement { push($1, $2); }
|
class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } }
|
||||||
| /* empty */ { init(); }
|
| /* empty */ { init(); }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
class_statement_list:
|
||||||
|
class_statement_list_ex
|
||||||
|
{ makeNop($nop, $this->lookaheadStartAttributes, $this->endAttributes);
|
||||||
|
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
||||||
|
;
|
||||||
|
|
||||||
class_statement:
|
class_statement:
|
||||||
variable_modifiers property_declaration_list ';'
|
variable_modifiers property_declaration_list ';'
|
||||||
{ $$ = Stmt\Property[$1, $2]; $this->checkProperty($$, #1); }
|
{ $$ = Stmt\Property[$1, $2]; $this->checkProperty($$, #1); }
|
||||||
@ -527,6 +542,7 @@ class_statement:
|
|||||||
{ $$ = Stmt\ClassMethod[$4, ['type' => $1, 'byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9]];
|
{ $$ = Stmt\ClassMethod[$4, ['type' => $1, 'byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9]];
|
||||||
$this->checkClassMethod($$, #1); }
|
$this->checkClassMethod($$, #1); }
|
||||||
| T_USE class_name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; }
|
| T_USE class_name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; }
|
||||||
|
| error { $$ = null; /* will be skipped */ }
|
||||||
;
|
;
|
||||||
|
|
||||||
trait_adaptations:
|
trait_adaptations:
|
||||||
@ -562,7 +578,7 @@ trait_method_reference:
|
|||||||
|
|
||||||
method_body:
|
method_body:
|
||||||
';' /* abstract method */ { $$ = null; }
|
';' /* abstract method */ { $$ = null; }
|
||||||
| '{' inner_statement_list '}' { $$ = $2; }
|
| block_or_error { $$ = $1; }
|
||||||
;
|
;
|
||||||
|
|
||||||
variable_modifiers:
|
variable_modifiers:
|
||||||
@ -708,11 +724,11 @@ expr:
|
|||||||
| T_YIELD expr T_DOUBLE_ARROW expr { $$ = Expr\Yield_[$4, $2]; }
|
| T_YIELD expr T_DOUBLE_ARROW expr { $$ = Expr\Yield_[$4, $2]; }
|
||||||
| T_YIELD_FROM expr { $$ = Expr\YieldFrom[$2]; }
|
| T_YIELD_FROM expr { $$ = Expr\YieldFrom[$2]; }
|
||||||
| T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type
|
| T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type
|
||||||
'{' inner_statement_list '}'
|
block_or_error
|
||||||
{ $$ = Expr\Closure[['static' => false, 'byRef' => $2, 'params' => $4, 'uses' => $6, 'returnType' => $7, 'stmts' => $9]]; }
|
{ $$ = Expr\Closure[['static' => false, 'byRef' => $2, 'params' => $4, 'uses' => $6, 'returnType' => $7, 'stmts' => $8]]; }
|
||||||
| T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type
|
| T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type
|
||||||
'{' inner_statement_list '}'
|
block_or_error
|
||||||
{ $$ = Expr\Closure[['static' => true, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $10]]; }
|
{ $$ = Expr\Closure[['static' => true, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $9]]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
anonymous_class:
|
anonymous_class:
|
||||||
@ -929,8 +945,10 @@ list_expr_elements:
|
|||||||
|
|
||||||
list_expr_element:
|
list_expr_element:
|
||||||
variable { $$ = Expr\ArrayItem[$1, null, false]; }
|
variable { $$ = Expr\ArrayItem[$1, null, false]; }
|
||||||
|
| '&' variable { $$ = Expr\ArrayItem[$2, null, true]; }
|
||||||
| list_expr { $$ = Expr\ArrayItem[$1, null, false]; }
|
| list_expr { $$ = Expr\ArrayItem[$1, null, false]; }
|
||||||
| expr T_DOUBLE_ARROW variable { $$ = Expr\ArrayItem[$3, $1, false]; }
|
| expr T_DOUBLE_ARROW variable { $$ = Expr\ArrayItem[$3, $1, false]; }
|
||||||
|
| expr T_DOUBLE_ARROW '&' variable { $$ = Expr\ArrayItem[$4, $1, true]; }
|
||||||
| expr T_DOUBLE_ARROW list_expr { $$ = Expr\ArrayItem[$3, $1, false]; }
|
| expr T_DOUBLE_ARROW list_expr { $$ = Expr\ArrayItem[$3, $1, false]; }
|
||||||
| /* empty */ { $$ = null; }
|
| /* empty */ { $$ = null; }
|
||||||
;
|
;
|
||||||
@ -940,8 +958,13 @@ array_pair_list:
|
|||||||
{ $$ = $1; $end = count($$)-1; if ($$[$end] === null) array_pop($$); }
|
{ $$ = $1; $end = count($$)-1; if ($$[$end] === null) array_pop($$); }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
comma_or_error:
|
||||||
|
','
|
||||||
|
| error
|
||||||
|
;
|
||||||
|
|
||||||
inner_array_pair_list:
|
inner_array_pair_list:
|
||||||
inner_array_pair_list ',' array_pair { push($1, $3); }
|
inner_array_pair_list comma_or_error array_pair { push($1, $3); }
|
||||||
| array_pair { init($1); }
|
| array_pair { init($1); }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@ -42,13 +42,13 @@ class Param implements PhpParser\Builder
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets type hint for the parameter.
|
* Sets type for the parameter.
|
||||||
*
|
*
|
||||||
* @param string|Node\Name|Node\NullableType $type Type hint to use
|
* @param string|Node\Name|Node\NullableType $type Parameter type
|
||||||
*
|
*
|
||||||
* @return $this The builder instance (for fluid interface)
|
* @return $this The builder instance (for fluid interface)
|
||||||
*/
|
*/
|
||||||
public function setTypeHint($type) {
|
public function setType($type) {
|
||||||
$this->type = BuilderHelpers::normalizeType($type);
|
$this->type = BuilderHelpers::normalizeType($type);
|
||||||
if ($this->type == 'void') {
|
if ($this->type == 'void') {
|
||||||
throw new \LogicException('Parameter type cannot be void');
|
throw new \LogicException('Parameter type cannot be void');
|
||||||
@ -57,6 +57,19 @@ class Param implements PhpParser\Builder
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets type for the parameter.
|
||||||
|
*
|
||||||
|
* @param string|Node\Name|Node\NullableType $type Parameter type
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*
|
||||||
|
* @deprecated Use setType() instead
|
||||||
|
*/
|
||||||
|
public function setTypeHint($type) {
|
||||||
|
return $this->setType($type);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make the parameter accept the value by reference.
|
* Make the parameter accept the value by reference.
|
||||||
*
|
*
|
||||||
|
64
lib/PhpParser/Builder/TraitUse.php
Normal file
64
lib/PhpParser/Builder/TraitUse.php
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Builder;
|
||||||
|
|
||||||
|
use PhpParser\Builder;
|
||||||
|
use PhpParser\BuilderHelpers;
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Stmt;
|
||||||
|
|
||||||
|
class TraitUse implements Builder
|
||||||
|
{
|
||||||
|
protected $traits = [];
|
||||||
|
protected $adaptations = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a trait use builder.
|
||||||
|
*
|
||||||
|
* @param Node\Name|string ...$traits Names of used traits
|
||||||
|
*/
|
||||||
|
public function __construct(...$traits) {
|
||||||
|
foreach ($traits as $trait) {
|
||||||
|
$this->and($trait);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds used trait.
|
||||||
|
*
|
||||||
|
* @param Node\Name|string $trait Trait name
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function and($trait) {
|
||||||
|
$this->traits[] = BuilderHelpers::normalizeName($trait);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds trait adaptation.
|
||||||
|
*
|
||||||
|
* @param Stmt\TraitUseAdaptation|Builder\TraitUseAdaptation $adaptation Trait adaptation
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function with($adaptation) {
|
||||||
|
$adaptation = BuilderHelpers::normalizeNode($adaptation);
|
||||||
|
|
||||||
|
if (!$adaptation instanceof Stmt\TraitUseAdaptation) {
|
||||||
|
throw new \LogicException('Adaptation must have type TraitUseAdaptation');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->adaptations[] = $adaptation;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the built node.
|
||||||
|
*
|
||||||
|
* @return Node The built node
|
||||||
|
*/
|
||||||
|
public function getNode() : Node {
|
||||||
|
return new Stmt\TraitUse($this->traits, $this->adaptations);
|
||||||
|
}
|
||||||
|
}
|
148
lib/PhpParser/Builder/TraitUseAdaptation.php
Normal file
148
lib/PhpParser/Builder/TraitUseAdaptation.php
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Builder;
|
||||||
|
|
||||||
|
use PhpParser\Builder;
|
||||||
|
use PhpParser\BuilderHelpers;
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Stmt;
|
||||||
|
|
||||||
|
class TraitUseAdaptation implements Builder
|
||||||
|
{
|
||||||
|
const TYPE_UNDEFINED = 0;
|
||||||
|
const TYPE_ALIAS = 1;
|
||||||
|
const TYPE_PRECEDENCE = 2;
|
||||||
|
|
||||||
|
/** @var int Type of building adaptation */
|
||||||
|
protected $type;
|
||||||
|
|
||||||
|
protected $trait;
|
||||||
|
protected $method;
|
||||||
|
|
||||||
|
protected $modifier = null;
|
||||||
|
protected $alias = null;
|
||||||
|
|
||||||
|
protected $insteadof = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a trait use adaptation builder.
|
||||||
|
*
|
||||||
|
* @param Node\Name|string|null $trait Name of adaptated trait
|
||||||
|
* @param Node\Identifier|string $method Name of adaptated method
|
||||||
|
*/
|
||||||
|
public function __construct($trait, $method) {
|
||||||
|
$this->type = self::TYPE_UNDEFINED;
|
||||||
|
|
||||||
|
$this->trait = is_null($trait)? null: BuilderHelpers::normalizeName($trait);
|
||||||
|
$this->method = BuilderHelpers::normalizeIdentifier($method);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets alias of method.
|
||||||
|
*
|
||||||
|
* @param Node\Identifier|string $alias Alias for adaptated method
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function as($alias) {
|
||||||
|
if ($this->type === self::TYPE_UNDEFINED) {
|
||||||
|
$this->type = self::TYPE_ALIAS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->type !== self::TYPE_ALIAS) {
|
||||||
|
throw new \LogicException('Cannot set alias for not alias adaptation buider');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->alias = $alias;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets adaptated method public.
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function makePublic() {
|
||||||
|
$this->setModifier(Stmt\Class_::MODIFIER_PUBLIC);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets adaptated method protected.
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function makeProtected() {
|
||||||
|
$this->setModifier(Stmt\Class_::MODIFIER_PROTECTED);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets adaptated method private.
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function makePrivate() {
|
||||||
|
$this->setModifier(Stmt\Class_::MODIFIER_PRIVATE);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds overwritten traits.
|
||||||
|
*
|
||||||
|
* @param Node\Name|string ...$traits Traits for overwrite
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function insteadof(...$traits) {
|
||||||
|
if ($this->type === self::TYPE_UNDEFINED) {
|
||||||
|
if (is_null($this->trait)) {
|
||||||
|
throw new \LogicException('Precedence adaptation must have trait');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->type = self::TYPE_PRECEDENCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->type !== self::TYPE_PRECEDENCE) {
|
||||||
|
throw new \LogicException('Cannot add overwritten traits for not precedence adaptation buider');
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($traits as $trait) {
|
||||||
|
$this->insteadof[] = BuilderHelpers::normalizeName($trait);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setModifier(int $modifier) {
|
||||||
|
if ($this->type === self::TYPE_UNDEFINED) {
|
||||||
|
$this->type = self::TYPE_ALIAS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->type !== self::TYPE_ALIAS) {
|
||||||
|
throw new \LogicException('Cannot set access modifier for not alias adaptation buider');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_null($this->modifier)) {
|
||||||
|
$this->modifier = $modifier;
|
||||||
|
} else {
|
||||||
|
throw new \LogicException('Multiple access type modifiers are not allowed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the built node.
|
||||||
|
*
|
||||||
|
* @return Node The built node
|
||||||
|
*/
|
||||||
|
public function getNode() : Node {
|
||||||
|
switch ($this->type) {
|
||||||
|
case self::TYPE_ALIAS:
|
||||||
|
return new Stmt\TraitUseAdaptation\Alias($this->trait, $this->method, $this->modifier, $this->alias);
|
||||||
|
case self::TYPE_PRECEDENCE:
|
||||||
|
return new Stmt\TraitUseAdaptation\Precedence($this->trait, $this->method, $this->insteadof);
|
||||||
|
default:
|
||||||
|
throw new \LogicException('Type of adaptation is not defined');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,10 @@ namespace PhpParser;
|
|||||||
use PhpParser\Node\Arg;
|
use PhpParser\Node\Arg;
|
||||||
use PhpParser\Node\Expr;
|
use PhpParser\Node\Expr;
|
||||||
use PhpParser\Node\Expr\BinaryOp\Concat;
|
use PhpParser\Node\Expr\BinaryOp\Concat;
|
||||||
|
use PhpParser\Node\Identifier;
|
||||||
|
use PhpParser\Node\Name;
|
||||||
use PhpParser\Node\Scalar\String_;
|
use PhpParser\Node\Scalar\String_;
|
||||||
|
use PhpParser\Node\Stmt;
|
||||||
use PhpParser\Node\Stmt\Use_;
|
use PhpParser\Node\Stmt\Use_;
|
||||||
|
|
||||||
class BuilderFactory
|
class BuilderFactory
|
||||||
@ -54,6 +57,34 @@ class BuilderFactory
|
|||||||
return new Builder\Trait_($name);
|
return new Builder\Trait_($name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a trait use builder.
|
||||||
|
*
|
||||||
|
* @param Node\Name|string ...$traits Trait names
|
||||||
|
*
|
||||||
|
* @return Builder\TraitUse The create 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 create trait use adaptation builder
|
||||||
|
*/
|
||||||
|
public function traitUseAdaptation($trait, $method = null) : Builder\TraitUseAdaptation {
|
||||||
|
if (is_null($method)) {
|
||||||
|
$method = $trait;
|
||||||
|
$trait = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Builder\TraitUseAdaptation($trait, $method);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a method builder.
|
* Creates a method builder.
|
||||||
*
|
*
|
||||||
@ -101,14 +132,36 @@ class BuilderFactory
|
|||||||
/**
|
/**
|
||||||
* Creates a namespace/class use builder.
|
* Creates a namespace/class use builder.
|
||||||
*
|
*
|
||||||
* @param string|Node\Name Name to alias
|
* @param Node\Name|string $name Name of the entity (namespace or class) to alias
|
||||||
*
|
*
|
||||||
* @return Builder\Use_ The create use builder
|
* @return Builder\Use_ The created use builder
|
||||||
*/
|
*/
|
||||||
public function use($name) : Builder\Use_ {
|
public function use($name) : Builder\Use_ {
|
||||||
return new Builder\Use_($name, Use_::TYPE_NORMAL);
|
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 node a for a literal value.
|
* Creates node a for a literal value.
|
||||||
*
|
*
|
||||||
@ -120,6 +173,21 @@ class BuilderFactory
|
|||||||
return BuilderHelpers::normalizeValue($value);
|
return BuilderHelpers::normalizeValue($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates variable node.
|
||||||
|
*
|
||||||
|
* @param string|Expr $name Name
|
||||||
|
*
|
||||||
|
* @return Expr\Variable
|
||||||
|
*/
|
||||||
|
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.
|
* Normalizes an argument list.
|
||||||
*
|
*
|
||||||
@ -141,6 +209,108 @@ class BuilderFactory
|
|||||||
return $normalizedArgs;
|
return $normalizedArgs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a function call node.
|
||||||
|
*
|
||||||
|
* @param string|Name|Expr $name Function name
|
||||||
|
* @param array $args Function arguments
|
||||||
|
*
|
||||||
|
* @return Expr\FuncCall
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*
|
||||||
|
* @return Expr\MethodCall
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*
|
||||||
|
* @return Expr\StaticCall
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*
|
||||||
|
* @return Expr\New_
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*
|
||||||
|
* @return Expr\ConstFetch
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*
|
||||||
|
* @return Expr\PropertyFetch
|
||||||
|
*/
|
||||||
|
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 $name Constant name
|
||||||
|
*
|
||||||
|
* @return Expr\ClassConstFetch
|
||||||
|
*/
|
||||||
|
public function classConstFetch($class, $name): Expr\ClassConstFetch {
|
||||||
|
return new Expr\ClassConstFetch(
|
||||||
|
BuilderHelpers::normalizeNameOrExpr($class),
|
||||||
|
BuilderHelpers::normalizeIdentifier($name)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates nested Concat nodes from a list of expressions.
|
* Creates nested Concat nodes from a list of expressions.
|
||||||
*
|
*
|
||||||
@ -161,12 +331,16 @@ class BuilderFactory
|
|||||||
return $lastConcat;
|
return $lastConcat;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function normalizeStringExpr($expr) {
|
/**
|
||||||
|
* @param string|Expr $expr
|
||||||
|
* @return Expr
|
||||||
|
*/
|
||||||
|
private function normalizeStringExpr($expr) : Expr {
|
||||||
if ($expr instanceof Expr) {
|
if ($expr instanceof Expr) {
|
||||||
return $expr;
|
return $expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_string($expr)) {
|
if (\is_string($expr)) {
|
||||||
return new String_($expr);
|
return new String_($expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,13 +56,74 @@ final class BuilderHelpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalizes a name: Converts plain string names to PhpParser\Node\Name.
|
* 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
|
* @param Name|string $name The name to normalize
|
||||||
*
|
*
|
||||||
* @return Name The normalized name
|
* @return Name The normalized name
|
||||||
*/
|
*/
|
||||||
public static function normalizeName($name) : Name {
|
public static function normalizeName($name) : Name {
|
||||||
|
return self::normalizeNameCommon($name, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
return self::normalizeNameCommon($name, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes a name: Converts string names to Name nodes, optionally allowing expressions.
|
||||||
|
*
|
||||||
|
* @param Expr|Name|string $name The name to normalize
|
||||||
|
* @param bool $allowExpr Whether to also allow expressions
|
||||||
|
*
|
||||||
|
* @return Name|Expr The normalized name, or expression (if allowed)
|
||||||
|
*/
|
||||||
|
private static function normalizeNameCommon($name, bool $allowExpr) {
|
||||||
if ($name instanceof Name) {
|
if ($name instanceof Name) {
|
||||||
return $name;
|
return $name;
|
||||||
} elseif (is_string($name)) {
|
} elseif (is_string($name)) {
|
||||||
@ -79,7 +140,16 @@ final class BuilderHelpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new \LogicException('Name must be a string or an instance of PhpParser\Node\Name');
|
if ($allowExpr) {
|
||||||
|
if ($name instanceof Expr) {
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
throw new \LogicException(
|
||||||
|
'Name must be a string or an instance of Node\Name or Node\Expr'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new \LogicException('Name must be a string or an instance of Node\Name');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,7 +15,7 @@ class Error extends \RuntimeException
|
|||||||
* (or start line of error -- deprecated)
|
* (or start line of error -- deprecated)
|
||||||
*/
|
*/
|
||||||
public function __construct(string $message, $attributes = []) {
|
public function __construct(string $message, $attributes = []) {
|
||||||
$this->rawMessage = (string) $message;
|
$this->rawMessage = $message;
|
||||||
if (is_array($attributes)) {
|
if (is_array($attributes)) {
|
||||||
$this->attributes = $attributes;
|
$this->attributes = $attributes;
|
||||||
} else {
|
} else {
|
||||||
@ -61,7 +61,7 @@ class Error extends \RuntimeException
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the attributes of the node/token the error occured at.
|
* Sets the attributes of the node/token the error occurred at.
|
||||||
*
|
*
|
||||||
* @param array $attributes
|
* @param array $attributes
|
||||||
*/
|
*/
|
||||||
@ -76,7 +76,7 @@ class Error extends \RuntimeException
|
|||||||
* @param string $message Error message
|
* @param string $message Error message
|
||||||
*/
|
*/
|
||||||
public function setRawMessage(string $message) {
|
public function setRawMessage(string $message) {
|
||||||
$this->rawMessage = (string) $message;
|
$this->rawMessage = $message;
|
||||||
$this->updateMessage();
|
$this->updateMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +86,7 @@ class Error extends \RuntimeException
|
|||||||
* @param int $line Error start line
|
* @param int $line Error start line
|
||||||
*/
|
*/
|
||||||
public function setStartLine(int $line) {
|
public function setStartLine(int $line) {
|
||||||
$this->attributes['startLine'] = (int) $line;
|
$this->attributes['startLine'] = $line;
|
||||||
$this->updateMessage();
|
$this->updateMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ abstract class BinaryOp extends Expr
|
|||||||
public $right;
|
public $right;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a bitwise and node.
|
* Constructs a binary operator node.
|
||||||
*
|
*
|
||||||
* @param Expr $left The left hand side expression
|
* @param Expr $left The left hand side expression
|
||||||
* @param Expr $right The right hand side expression
|
* @param Expr $right The right hand side expression
|
||||||
|
@ -16,8 +16,8 @@ class FuncCall extends Expr
|
|||||||
* Constructs a function call node.
|
* Constructs a function call node.
|
||||||
*
|
*
|
||||||
* @param Node\Name|Expr $name Function name
|
* @param Node\Name|Expr $name Function name
|
||||||
* @param Node\Arg[] $args Arguments
|
* @param Node\Arg[] $args Arguments
|
||||||
* @param array $attributes Additional attributes
|
* @param array $attributes Additional attributes
|
||||||
*/
|
*/
|
||||||
public function __construct($name, array $args = [], array $attributes = []) {
|
public function __construct($name, array $args = [], array $attributes = []) {
|
||||||
parent::__construct($attributes);
|
parent::__construct($attributes);
|
||||||
|
@ -12,7 +12,7 @@ class Param extends NodeAbstract
|
|||||||
public $byRef;
|
public $byRef;
|
||||||
/** @var bool Whether this is a variadic argument */
|
/** @var bool Whether this is a variadic argument */
|
||||||
public $variadic;
|
public $variadic;
|
||||||
/** @var Expr\Variable Parameter variable */
|
/** @var Expr\Variable|Expr\Error Parameter variable */
|
||||||
public $var;
|
public $var;
|
||||||
/** @var null|Expr Default value */
|
/** @var null|Expr Default value */
|
||||||
public $default;
|
public $default;
|
||||||
@ -20,7 +20,7 @@ class Param extends NodeAbstract
|
|||||||
/**
|
/**
|
||||||
* Constructs a parameter node.
|
* Constructs a parameter node.
|
||||||
*
|
*
|
||||||
* @param Expr\Variable $var Parameter variable
|
* @param Expr\Variable|Expr\Error $var Parameter variable
|
||||||
* @param null|Expr $default Default value
|
* @param null|Expr $default Default value
|
||||||
* @param null|string|Name|NullableType $type Typehint
|
* @param null|string|Name|NullableType $type Typehint
|
||||||
* @param bool $byRef Whether is passed by reference
|
* @param bool $byRef Whether is passed by reference
|
||||||
@ -28,7 +28,7 @@ class Param extends NodeAbstract
|
|||||||
* @param array $attributes Additional attributes
|
* @param array $attributes Additional attributes
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
Expr\Variable $var, Expr $default = null, $type = null,
|
$var, Expr $default = null, $type = null,
|
||||||
bool $byRef = false, bool $variadic = false, array $attributes = []
|
bool $byRef = false, bool $variadic = false, array $attributes = []
|
||||||
) {
|
) {
|
||||||
parent::__construct($attributes);
|
parent::__construct($attributes);
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace PhpParser;
|
namespace PhpParser;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
|
||||||
abstract class NodeAbstract implements Node, \JsonSerializable
|
abstract class NodeAbstract implements Node, \JsonSerializable
|
||||||
{
|
{
|
||||||
protected $attributes;
|
protected $attributes;
|
||||||
|
@ -31,16 +31,13 @@ class NodeTraverser implements NodeTraverserInterface
|
|||||||
const REMOVE_NODE = 3;
|
const REMOVE_NODE = 3;
|
||||||
|
|
||||||
/** @var NodeVisitor[] Visitors */
|
/** @var NodeVisitor[] Visitors */
|
||||||
protected $visitors;
|
protected $visitors = [];
|
||||||
|
|
||||||
/** @var bool Whether traversal should be stopped */
|
/** @var bool Whether traversal should be stopped */
|
||||||
protected $stopTraversal;
|
protected $stopTraversal;
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a node traverser.
|
|
||||||
*/
|
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
$this->visitors = [];
|
// for BC
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -115,6 +112,7 @@ class NodeTraverser implements NodeTraverserInterface
|
|||||||
$return = $visitor->enterNode($subNode);
|
$return = $visitor->enterNode($subNode);
|
||||||
if (null !== $return) {
|
if (null !== $return) {
|
||||||
if ($return instanceof Node) {
|
if ($return instanceof Node) {
|
||||||
|
$this->ensureReplacementReasonable($subNode, $return);
|
||||||
$subNode = $return;
|
$subNode = $return;
|
||||||
} elseif (self::DONT_TRAVERSE_CHILDREN === $return) {
|
} elseif (self::DONT_TRAVERSE_CHILDREN === $return) {
|
||||||
$traverseChildren = false;
|
$traverseChildren = false;
|
||||||
@ -140,6 +138,7 @@ class NodeTraverser implements NodeTraverserInterface
|
|||||||
$return = $visitor->leaveNode($subNode);
|
$return = $visitor->leaveNode($subNode);
|
||||||
if (null !== $return) {
|
if (null !== $return) {
|
||||||
if ($return instanceof Node) {
|
if ($return instanceof Node) {
|
||||||
|
$this->ensureReplacementReasonable($subNode, $return);
|
||||||
$subNode = $return;
|
$subNode = $return;
|
||||||
} elseif (self::STOP_TRAVERSAL === $return) {
|
} elseif (self::STOP_TRAVERSAL === $return) {
|
||||||
$this->stopTraversal = true;
|
$this->stopTraversal = true;
|
||||||
@ -179,6 +178,7 @@ class NodeTraverser implements NodeTraverserInterface
|
|||||||
$return = $visitor->enterNode($node);
|
$return = $visitor->enterNode($node);
|
||||||
if (null !== $return) {
|
if (null !== $return) {
|
||||||
if ($return instanceof Node) {
|
if ($return instanceof Node) {
|
||||||
|
$this->ensureReplacementReasonable($node, $return);
|
||||||
$node = $return;
|
$node = $return;
|
||||||
} elseif (self::DONT_TRAVERSE_CHILDREN === $return) {
|
} elseif (self::DONT_TRAVERSE_CHILDREN === $return) {
|
||||||
$traverseChildren = false;
|
$traverseChildren = false;
|
||||||
@ -204,6 +204,7 @@ class NodeTraverser implements NodeTraverserInterface
|
|||||||
$return = $visitor->leaveNode($node);
|
$return = $visitor->leaveNode($node);
|
||||||
if (null !== $return) {
|
if (null !== $return) {
|
||||||
if ($return instanceof Node) {
|
if ($return instanceof Node) {
|
||||||
|
$this->ensureReplacementReasonable($node, $return);
|
||||||
$node = $return;
|
$node = $return;
|
||||||
} elseif (\is_array($return)) {
|
} elseif (\is_array($return)) {
|
||||||
$doNodes[] = [$i, $return];
|
$doNodes[] = [$i, $return];
|
||||||
@ -239,4 +240,21 @@ class NodeTraverser implements NodeTraverserInterface
|
|||||||
|
|
||||||
return $nodes;
|
return $nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function ensureReplacementReasonable($old, $new) {
|
||||||
|
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()})"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -109,8 +109,6 @@ abstract class ParserAbstract implements Parser
|
|||||||
|
|
||||||
/** @var ErrorHandler Error handler */
|
/** @var ErrorHandler Error handler */
|
||||||
protected $errorHandler;
|
protected $errorHandler;
|
||||||
/** @var Error[] Errors collected during last parse */
|
|
||||||
protected $errors;
|
|
||||||
/** @var int Error state, used to avoid error floods */
|
/** @var int Error state, used to avoid error floods */
|
||||||
protected $errorState;
|
protected $errorState;
|
||||||
|
|
||||||
@ -129,7 +127,6 @@ abstract class ParserAbstract implements Parser
|
|||||||
*/
|
*/
|
||||||
public function __construct(Lexer $lexer, array $options = []) {
|
public function __construct(Lexer $lexer, array $options = []) {
|
||||||
$this->lexer = $lexer;
|
$this->lexer = $lexer;
|
||||||
$this->errors = [];
|
|
||||||
|
|
||||||
if (isset($options['throwOnError'])) {
|
if (isset($options['throwOnError'])) {
|
||||||
throw new \LogicException(
|
throw new \LogicException(
|
||||||
|
@ -198,6 +198,10 @@ class Standard extends PrettyPrinterAbstract
|
|||||||
return preg_match('/^-?[0-9]+$/', $stringValue) ? $stringValue . '.0' : $stringValue;
|
return preg_match('/^-?[0-9]+$/', $stringValue) ? $stringValue . '.0' : $stringValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function pScalar_EncapsedStringPart(Scalar\EncapsedStringPart $node) {
|
||||||
|
throw new \LogicException('Cannot directly print EncapsedStringPart');
|
||||||
|
}
|
||||||
|
|
||||||
// Assignments
|
// Assignments
|
||||||
|
|
||||||
protected function pExpr_Assign(Expr\Assign $node) {
|
protected function pExpr_Assign(Expr\Assign $node) {
|
||||||
|
@ -951,12 +951,15 @@ abstract class PrettyPrinterAbstract
|
|||||||
* @param string $append
|
* @param string $append
|
||||||
*/
|
*/
|
||||||
protected function safeAppend(string &$str, string $append) {
|
protected function safeAppend(string &$str, string $append) {
|
||||||
// $append must not be empty in this function
|
|
||||||
if ($str === "") {
|
if ($str === "") {
|
||||||
$str = $append;
|
$str = $append;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($append === "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!$this->labelCharMap[$append[0]]
|
if (!$this->labelCharMap[$append[0]]
|
||||||
|| !$this->labelCharMap[$str[\strlen($str) - 1]]) {
|
|| !$this->labelCharMap[$str[\strlen($str) - 1]]) {
|
||||||
$str .= $append;
|
$str .= $append;
|
||||||
|
@ -1,14 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<phpunit backupGlobals="false"
|
<phpunit backupGlobals="false"
|
||||||
backupStaticAttributes="false"
|
colors="true"
|
||||||
colors="false"
|
|
||||||
convertErrorsToExceptions="true"
|
|
||||||
convertNoticesToExceptions="true"
|
|
||||||
convertWarningsToExceptions="true"
|
|
||||||
processIsolation="false"
|
|
||||||
stopOnFailure="false"
|
|
||||||
syntaxCheck="false"
|
|
||||||
beStrictAboutTestsThatDoNotTestAnything="false"
|
beStrictAboutTestsThatDoNotTestAnything="false"
|
||||||
bootstrap="./test/bootstrap.php">
|
bootstrap="./test/bootstrap.php">
|
||||||
<testsuites>
|
<testsuites>
|
||||||
|
@ -153,7 +153,7 @@ DOC;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \LogicException
|
* @expectedException \LogicException
|
||||||
* @expectedExceptionMessage Name must be a string or an instance of PhpParser\Node\Name
|
* @expectedExceptionMessage Name must be a string or an instance of Node\Name
|
||||||
*/
|
*/
|
||||||
public function testInvalidName() {
|
public function testInvalidName() {
|
||||||
$this->createClassBuilder('Test')
|
$this->createClassBuilder('Test')
|
||||||
|
@ -80,9 +80,9 @@ class ParamTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider provideTestTypeHints
|
* @dataProvider provideTestTypes
|
||||||
*/
|
*/
|
||||||
public function testTypeHints($typeHint, $expectedType) {
|
public function testTypes($typeHint, $expectedType) {
|
||||||
$node = $this->createParamBuilder('test')
|
$node = $this->createParamBuilder('test')
|
||||||
->setTypeHint($typeHint)
|
->setTypeHint($typeHint)
|
||||||
->getNode()
|
->getNode()
|
||||||
@ -100,7 +100,7 @@ class ParamTest extends TestCase
|
|||||||
$this->assertEquals($expectedType, $type);
|
$this->assertEquals($expectedType, $type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function provideTestTypeHints() {
|
public function provideTestTypes() {
|
||||||
return [
|
return [
|
||||||
['array', new Node\Identifier('array')],
|
['array', new Node\Identifier('array')],
|
||||||
['callable', new Node\Identifier('callable')],
|
['callable', new Node\Identifier('callable')],
|
||||||
@ -134,7 +134,7 @@ class ParamTest extends TestCase
|
|||||||
* @expectedExceptionMessage Parameter type cannot be void
|
* @expectedExceptionMessage Parameter type cannot be void
|
||||||
*/
|
*/
|
||||||
public function testVoidTypeError() {
|
public function testVoidTypeError() {
|
||||||
$this->createParamBuilder('test')->setTypeHint('void');
|
$this->createParamBuilder('test')->setType('void');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -142,7 +142,7 @@ class ParamTest extends TestCase
|
|||||||
* @expectedExceptionMessage Type must be a string, or an instance of Name, Identifier or NullableType
|
* @expectedExceptionMessage Type must be a string, or an instance of Name, Identifier or NullableType
|
||||||
*/
|
*/
|
||||||
public function testInvalidTypeError() {
|
public function testInvalidTypeError() {
|
||||||
$this->createParamBuilder('test')->setTypeHint(new \stdClass);
|
$this->createParamBuilder('test')->setType(new \stdClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testByRef() {
|
public function testByRef() {
|
||||||
|
121
test/PhpParser/Builder/TraitUseAdaptationTest.php
Normal file
121
test/PhpParser/Builder/TraitUseAdaptationTest.php
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Builder;
|
||||||
|
|
||||||
|
use PhpParser\Comment;
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Name;
|
||||||
|
use PhpParser\Node\Stmt;
|
||||||
|
use PhpParser\Node\Stmt\Class_;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class TraitUseAdaptationTest extends TestCase
|
||||||
|
{
|
||||||
|
protected function createTraitUseAdaptationBuilder($trait, $method) {
|
||||||
|
return new TraitUseAdaptation($trait, $method);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAsMake() {
|
||||||
|
$builder = $this->createTraitUseAdaptationBuilder(null, 'foo');
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
new Stmt\TraitUseAdaptation\Alias(null, 'foo', null, 'bar'),
|
||||||
|
(clone $builder)->as('bar')->getNode()
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
new Stmt\TraitUseAdaptation\Alias(null, 'foo', Class_::MODIFIER_PUBLIC, null),
|
||||||
|
(clone $builder)->makePublic()->getNode()
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
new Stmt\TraitUseAdaptation\Alias(null, 'foo', Class_::MODIFIER_PROTECTED, null),
|
||||||
|
(clone $builder)->makeProtected()->getNode()
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
new Stmt\TraitUseAdaptation\Alias(null, 'foo', Class_::MODIFIER_PRIVATE, null),
|
||||||
|
(clone $builder)->makePrivate()->getNode()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInsteadof() {
|
||||||
|
$node = $this->createTraitUseAdaptationBuilder('SomeTrait', 'foo')
|
||||||
|
->insteadof('AnotherTrait')
|
||||||
|
->getNode()
|
||||||
|
;
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
new Stmt\TraitUseAdaptation\Precedence(
|
||||||
|
new Name('SomeTrait'),
|
||||||
|
'foo',
|
||||||
|
[new Name('AnotherTrait')]
|
||||||
|
),
|
||||||
|
$node
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \LogicException
|
||||||
|
* @expectedExceptionMessage Cannot set alias for not alias adaptation buider
|
||||||
|
*/
|
||||||
|
public function testAsOnNotAlias() {
|
||||||
|
$this->createTraitUseAdaptationBuilder('Test', 'foo')
|
||||||
|
->insteadof('AnotherTrait')
|
||||||
|
->as('bar')
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \LogicException
|
||||||
|
* @expectedExceptionMessage Cannot add overwritten traits for not precedence adaptation buider
|
||||||
|
*/
|
||||||
|
public function testInsteadofOnNotPrecedence() {
|
||||||
|
$this->createTraitUseAdaptationBuilder('Test', 'foo')
|
||||||
|
->as('bar')
|
||||||
|
->insteadof('AnotherTrait')
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \LogicException
|
||||||
|
* @expectedExceptionMessage Precedence adaptation must have trait
|
||||||
|
*/
|
||||||
|
public function testInsteadofWithoutTrait() {
|
||||||
|
$this->createTraitUseAdaptationBuilder(null, 'foo')
|
||||||
|
->insteadof('AnotherTrait')
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \LogicException
|
||||||
|
* @expectedExceptionMessage Cannot set access modifier for not alias adaptation buider
|
||||||
|
*/
|
||||||
|
public function testMakeOnNotAlias() {
|
||||||
|
$this->createTraitUseAdaptationBuilder('Test', 'foo')
|
||||||
|
->insteadof('AnotherTrait')
|
||||||
|
->makePublic()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \LogicException
|
||||||
|
* @expectedExceptionMessage Multiple access type modifiers are not allowed
|
||||||
|
*/
|
||||||
|
public function testMultipleMake() {
|
||||||
|
$this->createTraitUseAdaptationBuilder(null, 'foo')
|
||||||
|
->makePrivate()
|
||||||
|
->makePublic()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \LogicException
|
||||||
|
* @expectedExceptionMessage Type of adaptation is not defined
|
||||||
|
*/
|
||||||
|
public function testUndefinedType() {
|
||||||
|
$this->createTraitUseAdaptationBuilder(null, 'foo')
|
||||||
|
->getNode()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
57
test/PhpParser/Builder/TraitUseTest.php
Normal file
57
test/PhpParser/Builder/TraitUseTest.php
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Builder;
|
||||||
|
|
||||||
|
use PhpParser\Comment;
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Name;
|
||||||
|
use PhpParser\Node\Stmt;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class TraitUseTest extends TestCase
|
||||||
|
{
|
||||||
|
protected function createTraitUseBuilder(...$traits) {
|
||||||
|
return new TraitUse(...$traits);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAnd() {
|
||||||
|
$node = $this->createTraitUseBuilder('SomeTrait')
|
||||||
|
->and('AnotherTrait')
|
||||||
|
->getNode()
|
||||||
|
;
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
new Stmt\TraitUse([
|
||||||
|
new Name('SomeTrait'),
|
||||||
|
new Name('AnotherTrait')
|
||||||
|
]),
|
||||||
|
$node
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWith() {
|
||||||
|
$node = $this->createTraitUseBuilder('SomeTrait')
|
||||||
|
->with(new Stmt\TraitUseAdaptation\Alias(null, 'foo', null, 'bar'))
|
||||||
|
->with((new TraitUseAdaptation(null, 'test'))->as('baz'))
|
||||||
|
->getNode()
|
||||||
|
;
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
new Stmt\TraitUse([new Name('SomeTrait')], [
|
||||||
|
new Stmt\TraitUseAdaptation\Alias(null, 'foo', null, 'bar'),
|
||||||
|
new Stmt\TraitUseAdaptation\Alias(null, 'test', null, 'baz')
|
||||||
|
]),
|
||||||
|
$node
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \LogicException
|
||||||
|
* @expectedExceptionMessage Adaptation must have type TraitUseAdaptation
|
||||||
|
*/
|
||||||
|
public function testInvalidAdaptationNode() {
|
||||||
|
$this->createTraitUseBuilder('Test')
|
||||||
|
->with(new Stmt\Echo_([]))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
@ -26,5 +26,10 @@ class UseTest extends TestCase
|
|||||||
$this->assertEquals(new Stmt\Use_([
|
$this->assertEquals(new Stmt\Use_([
|
||||||
new Stmt\UseUse(new Name('foo\bar'), 'foo')
|
new Stmt\UseUse(new Name('foo\bar'), 'foo')
|
||||||
], Stmt\Use_::TYPE_FUNCTION), $node);
|
], Stmt\Use_::TYPE_FUNCTION), $node);
|
||||||
|
|
||||||
|
$node = $this->createUseBuilder('foo\BAR', Stmt\Use_::TYPE_CONSTANT)->as('FOO')->getNode();
|
||||||
|
$this->assertEquals(new Stmt\Use_([
|
||||||
|
new Stmt\UseUse(new Name('foo\BAR'), 'FOO')
|
||||||
|
], Stmt\Use_::TYPE_CONSTANT), $node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,12 @@ use PhpParser\Builder;
|
|||||||
use PhpParser\Node\Arg;
|
use PhpParser\Node\Arg;
|
||||||
use PhpParser\Node\Expr;
|
use PhpParser\Node\Expr;
|
||||||
use PhpParser\Node\Expr\BinaryOp\Concat;
|
use PhpParser\Node\Expr\BinaryOp\Concat;
|
||||||
|
use PhpParser\Node\Identifier;
|
||||||
|
use PhpParser\Node\Name;
|
||||||
|
use PhpParser\Node\Scalar\LNumber;
|
||||||
use PhpParser\Node\Scalar\String_;
|
use PhpParser\Node\Scalar\String_;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\Yaml\Tests\A;
|
||||||
|
|
||||||
class BuilderFactoryTest extends TestCase
|
class BuilderFactoryTest extends TestCase
|
||||||
{
|
{
|
||||||
@ -21,15 +25,17 @@ class BuilderFactoryTest extends TestCase
|
|||||||
|
|
||||||
public function provideTestFactory() {
|
public function provideTestFactory() {
|
||||||
return [
|
return [
|
||||||
['namespace', Builder\Namespace_::class],
|
['namespace', Builder\Namespace_::class],
|
||||||
['class', Builder\Class_::class],
|
['class', Builder\Class_::class],
|
||||||
['interface', Builder\Interface_::class],
|
['interface', Builder\Interface_::class],
|
||||||
['trait', Builder\Trait_::class],
|
['trait', Builder\Trait_::class],
|
||||||
['method', Builder\Method::class],
|
['method', Builder\Method::class],
|
||||||
['function', Builder\Function_::class],
|
['function', Builder\Function_::class],
|
||||||
['property', Builder\Property::class],
|
['property', Builder\Property::class],
|
||||||
['param', Builder\Param::class],
|
['param', Builder\Param::class],
|
||||||
['use', Builder\Use_::class],
|
['use', Builder\Use_::class],
|
||||||
|
['useFunction', Builder\Use_::class],
|
||||||
|
['useConst', Builder\Use_::class],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,23 +98,185 @@ class BuilderFactoryTest extends TestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testCalls() {
|
||||||
|
$factory = new BuilderFactory();
|
||||||
|
|
||||||
|
// Simple function call
|
||||||
|
$this->assertEquals(
|
||||||
|
new Expr\FuncCall(
|
||||||
|
new Name('var_dump'),
|
||||||
|
[new Arg(new String_('str'))]
|
||||||
|
),
|
||||||
|
$factory->funcCall('var_dump', ['str'])
|
||||||
|
);
|
||||||
|
// Dynamic function call
|
||||||
|
$this->assertEquals(
|
||||||
|
new Expr\FuncCall(new Expr\Variable('fn')),
|
||||||
|
$factory->funcCall(new Expr\Variable('fn'))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Simple method call
|
||||||
|
$this->assertEquals(
|
||||||
|
new Expr\MethodCall(
|
||||||
|
new Expr\Variable('obj'),
|
||||||
|
new Identifier('method'),
|
||||||
|
[new Arg(new LNumber(42))]
|
||||||
|
),
|
||||||
|
$factory->methodCall(new Expr\Variable('obj'), 'method', [42])
|
||||||
|
);
|
||||||
|
// Explicitly pass Identifier node
|
||||||
|
$this->assertEquals(
|
||||||
|
new Expr\MethodCall(
|
||||||
|
new Expr\Variable('obj'),
|
||||||
|
new Identifier('method')
|
||||||
|
),
|
||||||
|
$factory->methodCall(new Expr\Variable('obj'), new Identifier('method'))
|
||||||
|
);
|
||||||
|
// Dynamic method call
|
||||||
|
$this->assertEquals(
|
||||||
|
new Expr\MethodCall(
|
||||||
|
new Expr\Variable('obj'),
|
||||||
|
new Expr\Variable('method')
|
||||||
|
),
|
||||||
|
$factory->methodCall(new Expr\Variable('obj'), new Expr\Variable('method'))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Simple static method call
|
||||||
|
$this->assertEquals(
|
||||||
|
new Expr\StaticCall(
|
||||||
|
new Name\FullyQualified('Foo'),
|
||||||
|
new Identifier('bar'),
|
||||||
|
[new Arg(new Expr\Variable('baz'))]
|
||||||
|
),
|
||||||
|
$factory->staticCall('\Foo', 'bar', [new Expr\Variable('baz')])
|
||||||
|
);
|
||||||
|
// Dynamic static method call
|
||||||
|
$this->assertEquals(
|
||||||
|
new Expr\StaticCall(
|
||||||
|
new Expr\Variable('foo'),
|
||||||
|
new Expr\Variable('bar')
|
||||||
|
),
|
||||||
|
$factory->staticCall(new Expr\Variable('foo'), new Expr\Variable('bar'))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Simple new call
|
||||||
|
$this->assertEquals(
|
||||||
|
new Expr\New_(new Name\FullyQualified('stdClass')),
|
||||||
|
$factory->new('\stdClass')
|
||||||
|
);
|
||||||
|
// Dynamic new call
|
||||||
|
$this->assertEquals(
|
||||||
|
new Expr\New_(
|
||||||
|
new Expr\Variable('foo'),
|
||||||
|
[new Arg(new String_('bar'))]
|
||||||
|
),
|
||||||
|
$factory->new(new Expr\Variable('foo'), ['bar'])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testConstFetches() {
|
||||||
|
$factory = new BuilderFactory();
|
||||||
|
$this->assertEquals(
|
||||||
|
new Expr\ConstFetch(new Name('FOO')),
|
||||||
|
$factory->constFetch('FOO')
|
||||||
|
);
|
||||||
|
$this->assertEquals(
|
||||||
|
new Expr\ClassConstFetch(new Name('Foo'), new Identifier('BAR')),
|
||||||
|
$factory->classConstFetch('Foo', 'BAR')
|
||||||
|
);
|
||||||
|
$this->assertEquals(
|
||||||
|
new Expr\ClassConstFetch(new Expr\Variable('foo'), new Identifier('BAR')),
|
||||||
|
$factory->classConstFetch(new Expr\Variable('foo'), 'BAR')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVar() {
|
||||||
|
$factory = new BuilderFactory();
|
||||||
|
$this->assertEquals(
|
||||||
|
new Expr\Variable("foo"),
|
||||||
|
$factory->var("foo")
|
||||||
|
);
|
||||||
|
$this->assertEquals(
|
||||||
|
new Expr\Variable(new Expr\Variable("foo")),
|
||||||
|
$factory->var($factory->var("foo"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPropertyFetch() {
|
||||||
|
$f = new BuilderFactory();
|
||||||
|
$this->assertEquals(
|
||||||
|
new Expr\PropertyFetch(new Expr\Variable('foo'), 'bar'),
|
||||||
|
$f->propertyFetch($f->var('foo'), 'bar')
|
||||||
|
);
|
||||||
|
$this->assertEquals(
|
||||||
|
new Expr\PropertyFetch(new Expr\Variable('foo'), 'bar'),
|
||||||
|
$f->propertyFetch($f->var('foo'), new Identifier('bar'))
|
||||||
|
);
|
||||||
|
$this->assertEquals(
|
||||||
|
new Expr\PropertyFetch(new Expr\Variable('foo'), new Expr\Variable('bar')),
|
||||||
|
$f->propertyFetch($f->var('foo'), $f->var('bar'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \LogicException
|
||||||
|
* @expectedExceptionMessage Expected string or instance of Node\Identifier
|
||||||
|
*/
|
||||||
|
public function testInvalidIdentifier() {
|
||||||
|
(new BuilderFactory())->classConstFetch('Foo', new Expr\Variable('foo'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \LogicException
|
||||||
|
* @expectedExceptionMessage Expected string or instance of Node\Identifier or Node\Expr
|
||||||
|
*/
|
||||||
|
public function testInvalidIdentifierOrExpr() {
|
||||||
|
(new BuilderFactory())->staticCall('Foo', new Name('bar'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \LogicException
|
||||||
|
* @expectedExceptionMessage Name must be a string or an instance of Node\Name or Node\Expr
|
||||||
|
*/
|
||||||
|
public function testInvalidNameOrExpr() {
|
||||||
|
(new BuilderFactory())->funcCall(new Node\Stmt\Return_());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \LogicException
|
||||||
|
* @expectedExceptionMessage Variable name must be string or Expr
|
||||||
|
*/
|
||||||
|
public function testInvalidVar() {
|
||||||
|
(new BuilderFactory())->var(new Node\Stmt\Return_());
|
||||||
|
}
|
||||||
|
|
||||||
public function testIntegration() {
|
public function testIntegration() {
|
||||||
$factory = new BuilderFactory;
|
$factory = new BuilderFactory;
|
||||||
$node = $factory->namespace('Name\Space')
|
$node = $factory->namespace('Name\Space')
|
||||||
->addStmt($factory->use('Foo\Bar\SomeOtherClass'))
|
->addStmt($factory->use('Foo\Bar\SomeOtherClass'))
|
||||||
->addStmt($factory->use('Foo\Bar')->as('A'))
|
->addStmt($factory->use('Foo\Bar')->as('A'))
|
||||||
|
->addStmt($factory->useFunction('strlen'))
|
||||||
|
->addStmt($factory->useConst('PHP_VERSION'))
|
||||||
->addStmt($factory
|
->addStmt($factory
|
||||||
->class('SomeClass')
|
->class('SomeClass')
|
||||||
->extend('SomeOtherClass')
|
->extend('SomeOtherClass')
|
||||||
->implement('A\Few', '\Interfaces')
|
->implement('A\Few', '\Interfaces')
|
||||||
->makeAbstract()
|
->makeAbstract()
|
||||||
|
|
||||||
|
->addStmt($factory->useTrait('FirstTrait'))
|
||||||
|
|
||||||
|
->addStmt($factory->useTrait('SecondTrait', 'ThirdTrait')
|
||||||
|
->and('AnotherTrait')
|
||||||
|
->with($factory->traitUseAdaptation('foo')->as('bar'))
|
||||||
|
->with($factory->traitUseAdaptation('AnotherTrait', 'baz')->as('test'))
|
||||||
|
->with($factory->traitUseAdaptation('AnotherTrait', 'func')->insteadof('SecondTrait')))
|
||||||
|
|
||||||
->addStmt($factory->method('firstMethod'))
|
->addStmt($factory->method('firstMethod'))
|
||||||
|
|
||||||
->addStmt($factory->method('someMethod')
|
->addStmt($factory->method('someMethod')
|
||||||
->makePublic()
|
->makePublic()
|
||||||
->makeAbstract()
|
->makeAbstract()
|
||||||
->addParam($factory->param('someParam')->setTypeHint('SomeClass'))
|
->addParam($factory->param('someParam')->setType('SomeClass'))
|
||||||
->setDocComment('/**
|
->setDocComment('/**
|
||||||
* This method does something.
|
* This method does something.
|
||||||
*
|
*
|
||||||
@ -134,8 +302,16 @@ namespace Name\Space;
|
|||||||
|
|
||||||
use Foo\Bar\SomeOtherClass;
|
use Foo\Bar\SomeOtherClass;
|
||||||
use Foo\Bar as A;
|
use Foo\Bar as A;
|
||||||
|
use function strlen;
|
||||||
|
use const PHP_VERSION;
|
||||||
abstract class SomeClass extends SomeOtherClass implements A\Few, \Interfaces
|
abstract class SomeClass extends SomeOtherClass implements A\Few, \Interfaces
|
||||||
{
|
{
|
||||||
|
use FirstTrait;
|
||||||
|
use SecondTrait, ThirdTrait, AnotherTrait {
|
||||||
|
foo as bar;
|
||||||
|
AnotherTrait::baz as test;
|
||||||
|
AnotherTrait::func insteadof SecondTrait;
|
||||||
|
}
|
||||||
protected $someProperty;
|
protected $someProperty;
|
||||||
private $anotherProperty = array(1, 2, 3);
|
private $anotherProperty = array(1, 2, 3);
|
||||||
function firstMethod()
|
function firstMethod()
|
||||||
|
@ -52,7 +52,8 @@ class CodeParsingTest extends CodeTestAbstract
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getParseOutput(Parser $parser, $code, array $modes) {
|
// Must be public for updateTests.php
|
||||||
|
public function getParseOutput(Parser $parser, $code, array $modes) {
|
||||||
$dumpPositions = isset($modes['positions']);
|
$dumpPositions = isset($modes['positions']);
|
||||||
|
|
||||||
$errors = new ErrorHandler\Collecting;
|
$errors = new ErrorHandler\Collecting;
|
||||||
|
@ -258,7 +258,7 @@ class NodeTraverserTest extends TestCase
|
|||||||
$this->expectException(\LogicException::class);
|
$this->expectException(\LogicException::class);
|
||||||
$this->expectExceptionMessage($message);
|
$this->expectExceptionMessage($message);
|
||||||
|
|
||||||
$stmts = [new Node\Expr\UnaryMinus(new Node\Scalar\LNumber(42))];
|
$stmts = [new Node\Stmt\Expression(new Node\Scalar\LNumber(42))];
|
||||||
|
|
||||||
$traverser = new NodeTraverser();
|
$traverser = new NodeTraverser();
|
||||||
$traverser->addVisitor($visitor);
|
$traverser->addVisitor($visitor);
|
||||||
@ -268,19 +268,19 @@ class NodeTraverserTest extends TestCase
|
|||||||
public function provideTestInvalidReturn() {
|
public function provideTestInvalidReturn() {
|
||||||
$visitor1 = $this->getMockBuilder(NodeVisitor::class)->getMock();
|
$visitor1 = $this->getMockBuilder(NodeVisitor::class)->getMock();
|
||||||
$visitor1->expects($this->at(1))->method('enterNode')
|
$visitor1->expects($this->at(1))->method('enterNode')
|
||||||
->will($this->returnValue('foobar'));
|
->willReturn('foobar');
|
||||||
|
|
||||||
$visitor2 = $this->getMockBuilder(NodeVisitor::class)->getMock();
|
$visitor2 = $this->getMockBuilder(NodeVisitor::class)->getMock();
|
||||||
$visitor2->expects($this->at(2))->method('enterNode')
|
$visitor2->expects($this->at(2))->method('enterNode')
|
||||||
->will($this->returnValue('foobar'));
|
->willReturn('foobar');
|
||||||
|
|
||||||
$visitor3 = $this->getMockBuilder(NodeVisitor::class)->getMock();
|
$visitor3 = $this->getMockBuilder(NodeVisitor::class)->getMock();
|
||||||
$visitor3->expects($this->at(3))->method('leaveNode')
|
$visitor3->expects($this->at(3))->method('leaveNode')
|
||||||
->will($this->returnValue('foobar'));
|
->willReturn('foobar');
|
||||||
|
|
||||||
$visitor4 = $this->getMockBuilder(NodeVisitor::class)->getMock();
|
$visitor4 = $this->getMockBuilder(NodeVisitor::class)->getMock();
|
||||||
$visitor4->expects($this->at(4))->method('leaveNode')
|
$visitor4->expects($this->at(4))->method('leaveNode')
|
||||||
->will($this->returnValue('foobar'));
|
->willReturn('foobar');
|
||||||
|
|
||||||
$visitor5 = $this->getMockBuilder(NodeVisitor::class)->getMock();
|
$visitor5 = $this->getMockBuilder(NodeVisitor::class)->getMock();
|
||||||
$visitor5->expects($this->at(3))->method('leaveNode')
|
$visitor5->expects($this->at(3))->method('leaveNode')
|
||||||
@ -290,6 +290,14 @@ class NodeTraverserTest extends TestCase
|
|||||||
$visitor6->expects($this->at(4))->method('leaveNode')
|
$visitor6->expects($this->at(4))->method('leaveNode')
|
||||||
->willReturn(false);
|
->willReturn(false);
|
||||||
|
|
||||||
|
$visitor7 = $this->getMockBuilder(NodeVisitor::class)->getMock();
|
||||||
|
$visitor7->expects($this->at(1))->method('enterNode')
|
||||||
|
->willReturn(new Node\Scalar\LNumber(42));
|
||||||
|
|
||||||
|
$visitor8 = $this->getMockBuilder(NodeVisitor::class)->getMock();
|
||||||
|
$visitor8->expects($this->at(2))->method('enterNode')
|
||||||
|
->willReturn(new Node\Stmt\Return_());
|
||||||
|
|
||||||
return [
|
return [
|
||||||
[$visitor1, 'enterNode() returned invalid value of type string'],
|
[$visitor1, 'enterNode() returned invalid value of type string'],
|
||||||
[$visitor2, 'enterNode() returned invalid value of type string'],
|
[$visitor2, 'enterNode() returned invalid value of type string'],
|
||||||
@ -297,6 +305,8 @@ class NodeTraverserTest extends TestCase
|
|||||||
[$visitor4, 'leaveNode() returned invalid value of type string'],
|
[$visitor4, 'leaveNode() returned invalid value of type string'],
|
||||||
[$visitor5, 'leaveNode() may only return an array if the parent structure is an array'],
|
[$visitor5, 'leaveNode() may only return an array if the parent structure is an array'],
|
||||||
[$visitor6, 'bool(false) return from leaveNode() no longer supported. Return NodeTraverser::REMOVE_NODE instead'],
|
[$visitor6, 'bool(false) return from leaveNode() no longer supported. Return NodeTraverser::REMOVE_NODE instead'],
|
||||||
|
[$visitor7, 'Trying to replace statement (Stmt_Expression) with expression (Scalar_LNumber). Are you missing a Stmt_Expression wrapper?'],
|
||||||
|
[$visitor8, 'Trying to replace expression (Scalar_LNumber) with statement (Stmt_Return)'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,6 @@ class FirstFindingVisitorTest extends TestCase
|
|||||||
$stmts = [new Node\Stmt\Expression($assign)];
|
$stmts = [new Node\Stmt\Expression($assign)];
|
||||||
|
|
||||||
$traverser->traverse($stmts);
|
$traverser->traverse($stmts);
|
||||||
$this->assertSame(null, $visitor->getFoundNode());
|
$this->assertNull($visitor->getFoundNode());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -208,6 +208,16 @@ class PrettyPrinterTest extends CodeTestAbstract
|
|||||||
$prettyPrinter->prettyPrint($stmts);
|
$prettyPrinter->prettyPrint($stmts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \LogicException
|
||||||
|
* @expectedExceptionMessage Cannot directly print EncapsedStringPart
|
||||||
|
*/
|
||||||
|
public function testPrettyPrintEncapsedStringPart() {
|
||||||
|
$expr = new Node\Scalar\EncapsedStringPart('foo');
|
||||||
|
$prettyPrinter = new PrettyPrinter\Standard;
|
||||||
|
$prettyPrinter->prettyPrintExpr($expr);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider provideTestFormatPreservingPrint
|
* @dataProvider provideTestFormatPreservingPrint
|
||||||
* @covers \PhpParser\PrettyPrinter\Standard<extended>
|
* @covers \PhpParser\PrettyPrinter\Standard<extended>
|
||||||
|
11
test/code/formatPreservation/nopCommentAtEnd.test
Normal file
11
test/code/formatPreservation/nopCommentAtEnd.test
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
Nop statement with comment at end (#513)
|
||||||
|
-----
|
||||||
|
<?php
|
||||||
|
$foo;
|
||||||
|
$bar;
|
||||||
|
-----
|
||||||
|
$stmts[1] = new Stmt\Nop(['comments' => [new Comment('//Some comment here')]]);
|
||||||
|
-----
|
||||||
|
<?php
|
||||||
|
$foo;
|
||||||
|
//Some comment here
|
37
test/code/parser/commentAtEndOfClass.test
Normal file
37
test/code/parser/commentAtEndOfClass.test
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
Comment at end of class (#509)
|
||||||
|
-----
|
||||||
|
<?php
|
||||||
|
class MyClass {
|
||||||
|
protected $a;
|
||||||
|
// my comment
|
||||||
|
}
|
||||||
|
-----
|
||||||
|
array(
|
||||||
|
0: Stmt_Class(
|
||||||
|
flags: 0
|
||||||
|
name: Identifier(
|
||||||
|
name: MyClass
|
||||||
|
)
|
||||||
|
extends: null
|
||||||
|
implements: array(
|
||||||
|
)
|
||||||
|
stmts: array(
|
||||||
|
0: Stmt_Property(
|
||||||
|
flags: MODIFIER_PROTECTED (2)
|
||||||
|
props: array(
|
||||||
|
0: Stmt_PropertyProperty(
|
||||||
|
name: VarLikeIdentifier(
|
||||||
|
name: a
|
||||||
|
)
|
||||||
|
default: null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
1: Stmt_Nop(
|
||||||
|
comments: array(
|
||||||
|
0: // my comment
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
@ -613,11 +613,8 @@ A trailing comma is not allowed here from 11:25 to 11:25
|
|||||||
A trailing comma is not allowed here from 13:17 to 13:17
|
A trailing comma is not allowed here from 13:17 to 13:17
|
||||||
A trailing comma is not allowed here from 14:14 to 14:14
|
A trailing comma is not allowed here from 14:14 to 14:14
|
||||||
A trailing comma is not allowed here from 16:22 to 16:22
|
A trailing comma is not allowed here from 16:22 to 16:22
|
||||||
A trailing comma is not allowed here from 18:9 to 18:9
|
|
||||||
A trailing comma is not allowed here from 19:9 to 19:9
|
|
||||||
A trailing comma is not allowed here from 21:13 to 21:13
|
A trailing comma is not allowed here from 21:13 to 21:13
|
||||||
A trailing comma is not allowed here from 23:16 to 23:16
|
A trailing comma is not allowed here from 23:16 to 23:16
|
||||||
A trailing comma is not allowed here from 24:7 to 24:7
|
|
||||||
A trailing comma is not allowed here from 25:10 to 25:10
|
A trailing comma is not allowed here from 25:10 to 25:10
|
||||||
A trailing comma is not allowed here from 26:10 to 26:10
|
A trailing comma is not allowed here from 26:10 to 26:10
|
||||||
A trailing comma is not allowed here from 27:8 to 27:8
|
A trailing comma is not allowed here from 27:8 to 27:8
|
||||||
@ -955,4 +952,425 @@ array(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
-----
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Foo {
|
||||||
|
publi $foo;
|
||||||
|
public $bar;
|
||||||
|
}
|
||||||
|
-----
|
||||||
|
!!php7
|
||||||
|
Syntax error, unexpected T_STRING from 4:5 to 4:9
|
||||||
|
array(
|
||||||
|
0: Stmt_Class(
|
||||||
|
flags: 0
|
||||||
|
name: Identifier(
|
||||||
|
name: Foo
|
||||||
|
)
|
||||||
|
extends: null
|
||||||
|
implements: array(
|
||||||
|
)
|
||||||
|
stmts: array(
|
||||||
|
0: Stmt_Property(
|
||||||
|
flags: MODIFIER_PUBLIC (1)
|
||||||
|
props: array(
|
||||||
|
0: Stmt_PropertyProperty(
|
||||||
|
name: VarLikeIdentifier(
|
||||||
|
name: bar
|
||||||
|
)
|
||||||
|
default: null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
-----
|
||||||
|
<?php
|
||||||
|
|
||||||
|
foreach ($foo) { $bar; }
|
||||||
|
foreach ($foo as ) { $bar; }
|
||||||
|
-----
|
||||||
|
!!php7
|
||||||
|
Syntax error, unexpected ')' from 3:14 to 3:14
|
||||||
|
Syntax error, unexpected ')' from 4:18 to 4:18
|
||||||
|
array(
|
||||||
|
0: Stmt_Foreach(
|
||||||
|
expr: Expr_Variable(
|
||||||
|
name: foo
|
||||||
|
)
|
||||||
|
keyVar: null
|
||||||
|
byRef: false
|
||||||
|
valueVar: Expr_Error(
|
||||||
|
)
|
||||||
|
stmts: array(
|
||||||
|
0: Stmt_Expression(
|
||||||
|
expr: Expr_Variable(
|
||||||
|
name: bar
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
1: Stmt_Foreach(
|
||||||
|
expr: Expr_Variable(
|
||||||
|
name: foo
|
||||||
|
)
|
||||||
|
keyVar: null
|
||||||
|
byRef: false
|
||||||
|
valueVar: Expr_Error(
|
||||||
|
)
|
||||||
|
stmts: array(
|
||||||
|
0: Stmt_Expression(
|
||||||
|
expr: Expr_Variable(
|
||||||
|
name: bar
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
-----
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function foo(Type) {
|
||||||
|
$foo;
|
||||||
|
}
|
||||||
|
|
||||||
|
function foo(Type1 $foo, Type2) {
|
||||||
|
$bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
function foo(...) {
|
||||||
|
$baz;
|
||||||
|
}
|
||||||
|
|
||||||
|
function foo(&) {
|
||||||
|
$qux;
|
||||||
|
}
|
||||||
|
|
||||||
|
function foo(Bar)
|
||||||
|
|
||||||
|
class Bar {
|
||||||
|
function foo(Baz)
|
||||||
|
}
|
||||||
|
|
||||||
|
function(Foo);
|
||||||
|
-----
|
||||||
|
!!php7
|
||||||
|
Syntax error, unexpected ')', expecting T_VARIABLE from 3:18 to 3:18
|
||||||
|
Syntax error, unexpected ')', expecting T_VARIABLE from 7:31 to 7:31
|
||||||
|
Syntax error, unexpected ')', expecting T_VARIABLE from 11:17 to 11:17
|
||||||
|
Syntax error, unexpected ')', expecting T_VARIABLE from 15:15 to 15:15
|
||||||
|
Syntax error, unexpected ')', expecting T_VARIABLE from 19:17 to 19:17
|
||||||
|
Syntax error, unexpected ')', expecting T_VARIABLE from 22:21 to 22:21
|
||||||
|
Syntax error, unexpected ')', expecting T_VARIABLE from 25:13 to 25:13
|
||||||
|
array(
|
||||||
|
0: Stmt_Function(
|
||||||
|
byRef: false
|
||||||
|
name: Identifier(
|
||||||
|
name: foo
|
||||||
|
)
|
||||||
|
params: array(
|
||||||
|
0: Param(
|
||||||
|
type: Name(
|
||||||
|
parts: array(
|
||||||
|
0: Type
|
||||||
|
)
|
||||||
|
)
|
||||||
|
byRef: false
|
||||||
|
variadic: false
|
||||||
|
var: Expr_Error(
|
||||||
|
)
|
||||||
|
default: null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
returnType: null
|
||||||
|
stmts: array(
|
||||||
|
0: Stmt_Expression(
|
||||||
|
expr: Expr_Variable(
|
||||||
|
name: foo
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
1: Stmt_Function(
|
||||||
|
byRef: false
|
||||||
|
name: Identifier(
|
||||||
|
name: foo
|
||||||
|
)
|
||||||
|
params: array(
|
||||||
|
0: Param(
|
||||||
|
type: Name(
|
||||||
|
parts: array(
|
||||||
|
0: Type1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
byRef: false
|
||||||
|
variadic: false
|
||||||
|
var: Expr_Variable(
|
||||||
|
name: foo
|
||||||
|
)
|
||||||
|
default: null
|
||||||
|
)
|
||||||
|
1: Param(
|
||||||
|
type: Name(
|
||||||
|
parts: array(
|
||||||
|
0: Type2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
byRef: false
|
||||||
|
variadic: false
|
||||||
|
var: Expr_Error(
|
||||||
|
)
|
||||||
|
default: null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
returnType: null
|
||||||
|
stmts: array(
|
||||||
|
0: Stmt_Expression(
|
||||||
|
expr: Expr_Variable(
|
||||||
|
name: bar
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
2: Stmt_Function(
|
||||||
|
byRef: false
|
||||||
|
name: Identifier(
|
||||||
|
name: foo
|
||||||
|
)
|
||||||
|
params: array(
|
||||||
|
0: Param(
|
||||||
|
type: null
|
||||||
|
byRef: false
|
||||||
|
variadic: true
|
||||||
|
var: Expr_Error(
|
||||||
|
)
|
||||||
|
default: null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
returnType: null
|
||||||
|
stmts: array(
|
||||||
|
0: Stmt_Expression(
|
||||||
|
expr: Expr_Variable(
|
||||||
|
name: baz
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
3: Stmt_Function(
|
||||||
|
byRef: false
|
||||||
|
name: Identifier(
|
||||||
|
name: foo
|
||||||
|
)
|
||||||
|
params: array(
|
||||||
|
0: Param(
|
||||||
|
type: null
|
||||||
|
byRef: true
|
||||||
|
variadic: false
|
||||||
|
var: Expr_Error(
|
||||||
|
)
|
||||||
|
default: null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
returnType: null
|
||||||
|
stmts: array(
|
||||||
|
0: Stmt_Expression(
|
||||||
|
expr: Expr_Variable(
|
||||||
|
name: qux
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
4: Stmt_Function(
|
||||||
|
byRef: false
|
||||||
|
name: Identifier(
|
||||||
|
name: foo
|
||||||
|
)
|
||||||
|
params: array(
|
||||||
|
0: Param(
|
||||||
|
type: Name(
|
||||||
|
parts: array(
|
||||||
|
0: Bar
|
||||||
|
)
|
||||||
|
)
|
||||||
|
byRef: false
|
||||||
|
variadic: false
|
||||||
|
var: Expr_Error(
|
||||||
|
)
|
||||||
|
default: null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
returnType: null
|
||||||
|
stmts: array(
|
||||||
|
)
|
||||||
|
)
|
||||||
|
5: Stmt_Class(
|
||||||
|
flags: 0
|
||||||
|
name: Identifier(
|
||||||
|
name: Bar
|
||||||
|
)
|
||||||
|
extends: null
|
||||||
|
implements: array(
|
||||||
|
)
|
||||||
|
stmts: array(
|
||||||
|
0: Stmt_ClassMethod(
|
||||||
|
flags: 0
|
||||||
|
byRef: false
|
||||||
|
name: Identifier(
|
||||||
|
name: foo
|
||||||
|
)
|
||||||
|
params: array(
|
||||||
|
0: Param(
|
||||||
|
type: Name(
|
||||||
|
parts: array(
|
||||||
|
0: Baz
|
||||||
|
)
|
||||||
|
)
|
||||||
|
byRef: false
|
||||||
|
variadic: false
|
||||||
|
var: Expr_Error(
|
||||||
|
)
|
||||||
|
default: null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
returnType: null
|
||||||
|
stmts: array(
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
6: Stmt_Expression(
|
||||||
|
expr: Expr_Closure(
|
||||||
|
static: false
|
||||||
|
byRef: false
|
||||||
|
params: array(
|
||||||
|
0: Param(
|
||||||
|
type: Name(
|
||||||
|
parts: array(
|
||||||
|
0: Foo
|
||||||
|
)
|
||||||
|
)
|
||||||
|
byRef: false
|
||||||
|
variadic: false
|
||||||
|
var: Expr_Error(
|
||||||
|
)
|
||||||
|
default: null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
uses: array(
|
||||||
|
)
|
||||||
|
returnType: null
|
||||||
|
stmts: array(
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
-----
|
||||||
|
<?php
|
||||||
|
$array = [
|
||||||
|
$this->value $oopsAnotherValue->get()
|
||||||
|
];
|
||||||
|
$array = [
|
||||||
|
$value $oopsAnotherValue
|
||||||
|
];
|
||||||
|
$array = [
|
||||||
|
'key' => $value $oopsAnotherValue
|
||||||
|
];
|
||||||
|
-----
|
||||||
|
!!php7
|
||||||
|
Syntax error, unexpected T_VARIABLE, expecting ',' or ')' or ']' from 3:18 to 3:34
|
||||||
|
Syntax error, unexpected T_VARIABLE, expecting ',' or ')' or ']' from 6:12 to 6:28
|
||||||
|
Syntax error, unexpected T_VARIABLE, expecting ',' or ')' or ']' from 9:21 to 9:37
|
||||||
|
array(
|
||||||
|
0: Stmt_Expression(
|
||||||
|
expr: Expr_Assign(
|
||||||
|
var: Expr_Variable(
|
||||||
|
name: array
|
||||||
|
)
|
||||||
|
expr: Expr_Array(
|
||||||
|
items: array(
|
||||||
|
0: Expr_ArrayItem(
|
||||||
|
key: null
|
||||||
|
value: Expr_PropertyFetch(
|
||||||
|
var: Expr_Variable(
|
||||||
|
name: this
|
||||||
|
)
|
||||||
|
name: Identifier(
|
||||||
|
name: value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
byRef: false
|
||||||
|
)
|
||||||
|
1: Expr_ArrayItem(
|
||||||
|
key: null
|
||||||
|
value: Expr_MethodCall(
|
||||||
|
var: Expr_Variable(
|
||||||
|
name: oopsAnotherValue
|
||||||
|
)
|
||||||
|
name: Identifier(
|
||||||
|
name: get
|
||||||
|
)
|
||||||
|
args: array(
|
||||||
|
)
|
||||||
|
)
|
||||||
|
byRef: false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
1: Stmt_Expression(
|
||||||
|
expr: Expr_Assign(
|
||||||
|
var: Expr_Variable(
|
||||||
|
name: array
|
||||||
|
)
|
||||||
|
expr: Expr_Array(
|
||||||
|
items: array(
|
||||||
|
0: Expr_ArrayItem(
|
||||||
|
key: null
|
||||||
|
value: Expr_Variable(
|
||||||
|
name: value
|
||||||
|
)
|
||||||
|
byRef: false
|
||||||
|
)
|
||||||
|
1: Expr_ArrayItem(
|
||||||
|
key: null
|
||||||
|
value: Expr_Variable(
|
||||||
|
name: oopsAnotherValue
|
||||||
|
)
|
||||||
|
byRef: false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
2: Stmt_Expression(
|
||||||
|
expr: Expr_Assign(
|
||||||
|
var: Expr_Variable(
|
||||||
|
name: array
|
||||||
|
)
|
||||||
|
expr: Expr_Array(
|
||||||
|
items: array(
|
||||||
|
0: Expr_ArrayItem(
|
||||||
|
key: Scalar_String(
|
||||||
|
value: key
|
||||||
|
)
|
||||||
|
value: Expr_Variable(
|
||||||
|
name: value
|
||||||
|
)
|
||||||
|
byRef: false
|
||||||
|
)
|
||||||
|
1: Expr_ArrayItem(
|
||||||
|
key: null
|
||||||
|
value: Expr_Variable(
|
||||||
|
name: oopsAnotherValue
|
||||||
|
)
|
||||||
|
byRef: false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
88
test/code/parser/expr/listReferences.test
Normal file
88
test/code/parser/expr/listReferences.test
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
List reference assignments (PHP 7.3)
|
||||||
|
-----
|
||||||
|
<?php
|
||||||
|
|
||||||
|
list(&$v) = $x;
|
||||||
|
list('k' => &$v) = $x;
|
||||||
|
[&$v] = $x;
|
||||||
|
['k' => &$v] = $x;
|
||||||
|
-----
|
||||||
|
!!php7
|
||||||
|
array(
|
||||||
|
0: Stmt_Expression(
|
||||||
|
expr: Expr_Assign(
|
||||||
|
var: Expr_List(
|
||||||
|
items: array(
|
||||||
|
0: Expr_ArrayItem(
|
||||||
|
key: null
|
||||||
|
value: Expr_Variable(
|
||||||
|
name: v
|
||||||
|
)
|
||||||
|
byRef: true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
expr: Expr_Variable(
|
||||||
|
name: x
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
1: Stmt_Expression(
|
||||||
|
expr: Expr_Assign(
|
||||||
|
var: Expr_List(
|
||||||
|
items: array(
|
||||||
|
0: Expr_ArrayItem(
|
||||||
|
key: Scalar_String(
|
||||||
|
value: k
|
||||||
|
)
|
||||||
|
value: Expr_Variable(
|
||||||
|
name: v
|
||||||
|
)
|
||||||
|
byRef: true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
expr: Expr_Variable(
|
||||||
|
name: x
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
2: Stmt_Expression(
|
||||||
|
expr: Expr_Assign(
|
||||||
|
var: Expr_Array(
|
||||||
|
items: array(
|
||||||
|
0: Expr_ArrayItem(
|
||||||
|
key: null
|
||||||
|
value: Expr_Variable(
|
||||||
|
name: v
|
||||||
|
)
|
||||||
|
byRef: true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
expr: Expr_Variable(
|
||||||
|
name: x
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
3: Stmt_Expression(
|
||||||
|
expr: Expr_Assign(
|
||||||
|
var: Expr_Array(
|
||||||
|
items: array(
|
||||||
|
0: Expr_ArrayItem(
|
||||||
|
key: Scalar_String(
|
||||||
|
value: k
|
||||||
|
)
|
||||||
|
value: Expr_Variable(
|
||||||
|
name: v
|
||||||
|
)
|
||||||
|
byRef: true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
expr: Expr_Variable(
|
||||||
|
name: x
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
140
test/code/parser/expr/trailingCommas.test
Normal file
140
test/code/parser/expr/trailingCommas.test
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
PHP 7.3 trailing comma additions
|
||||||
|
-----
|
||||||
|
<?php
|
||||||
|
|
||||||
|
foo($a, $b, );
|
||||||
|
$foo->bar($a, $b, );
|
||||||
|
Foo::bar($a, $b, );
|
||||||
|
new Foo($a, $b, );
|
||||||
|
unset($a, $b, );
|
||||||
|
isset($a, $b, );
|
||||||
|
-----
|
||||||
|
!!php7
|
||||||
|
array(
|
||||||
|
0: Stmt_Expression(
|
||||||
|
expr: Expr_FuncCall(
|
||||||
|
name: Name(
|
||||||
|
parts: array(
|
||||||
|
0: foo
|
||||||
|
)
|
||||||
|
)
|
||||||
|
args: array(
|
||||||
|
0: Arg(
|
||||||
|
value: Expr_Variable(
|
||||||
|
name: a
|
||||||
|
)
|
||||||
|
byRef: false
|
||||||
|
unpack: false
|
||||||
|
)
|
||||||
|
1: Arg(
|
||||||
|
value: Expr_Variable(
|
||||||
|
name: b
|
||||||
|
)
|
||||||
|
byRef: false
|
||||||
|
unpack: false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
1: Stmt_Expression(
|
||||||
|
expr: Expr_MethodCall(
|
||||||
|
var: Expr_Variable(
|
||||||
|
name: foo
|
||||||
|
)
|
||||||
|
name: Identifier(
|
||||||
|
name: bar
|
||||||
|
)
|
||||||
|
args: array(
|
||||||
|
0: Arg(
|
||||||
|
value: Expr_Variable(
|
||||||
|
name: a
|
||||||
|
)
|
||||||
|
byRef: false
|
||||||
|
unpack: false
|
||||||
|
)
|
||||||
|
1: Arg(
|
||||||
|
value: Expr_Variable(
|
||||||
|
name: b
|
||||||
|
)
|
||||||
|
byRef: false
|
||||||
|
unpack: false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
2: Stmt_Expression(
|
||||||
|
expr: Expr_StaticCall(
|
||||||
|
class: Name(
|
||||||
|
parts: array(
|
||||||
|
0: Foo
|
||||||
|
)
|
||||||
|
)
|
||||||
|
name: Identifier(
|
||||||
|
name: bar
|
||||||
|
)
|
||||||
|
args: array(
|
||||||
|
0: Arg(
|
||||||
|
value: Expr_Variable(
|
||||||
|
name: a
|
||||||
|
)
|
||||||
|
byRef: false
|
||||||
|
unpack: false
|
||||||
|
)
|
||||||
|
1: Arg(
|
||||||
|
value: Expr_Variable(
|
||||||
|
name: b
|
||||||
|
)
|
||||||
|
byRef: false
|
||||||
|
unpack: false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
3: Stmt_Expression(
|
||||||
|
expr: Expr_New(
|
||||||
|
class: Name(
|
||||||
|
parts: array(
|
||||||
|
0: Foo
|
||||||
|
)
|
||||||
|
)
|
||||||
|
args: array(
|
||||||
|
0: Arg(
|
||||||
|
value: Expr_Variable(
|
||||||
|
name: a
|
||||||
|
)
|
||||||
|
byRef: false
|
||||||
|
unpack: false
|
||||||
|
)
|
||||||
|
1: Arg(
|
||||||
|
value: Expr_Variable(
|
||||||
|
name: b
|
||||||
|
)
|
||||||
|
byRef: false
|
||||||
|
unpack: false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
4: Stmt_Unset(
|
||||||
|
vars: array(
|
||||||
|
0: Expr_Variable(
|
||||||
|
name: a
|
||||||
|
)
|
||||||
|
1: Expr_Variable(
|
||||||
|
name: b
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
5: Stmt_Expression(
|
||||||
|
expr: Expr_Isset(
|
||||||
|
vars: array(
|
||||||
|
0: Expr_Variable(
|
||||||
|
name: a
|
||||||
|
)
|
||||||
|
1: Expr_Variable(
|
||||||
|
name: b
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
@ -222,6 +222,11 @@ array(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
8: Stmt_Nop(
|
||||||
|
comments: array(
|
||||||
|
0: // __halt_compiler does not work
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
1: Stmt_Expression(
|
1: Stmt_Expression(
|
||||||
|
@ -4,6 +4,8 @@ Switch
|
|||||||
|
|
||||||
switch ($a) {
|
switch ($a) {
|
||||||
case 0:
|
case 0:
|
||||||
|
break;
|
||||||
|
// Comment
|
||||||
case 1;
|
case 1;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
@ -27,6 +29,9 @@ array(
|
|||||||
value: 0
|
value: 0
|
||||||
)
|
)
|
||||||
stmts: array(
|
stmts: array(
|
||||||
|
0: Stmt_Break(
|
||||||
|
num: null
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
1: Stmt_Case(
|
1: Stmt_Case(
|
||||||
@ -35,6 +40,9 @@ array(
|
|||||||
)
|
)
|
||||||
stmts: array(
|
stmts: array(
|
||||||
)
|
)
|
||||||
|
comments: array(
|
||||||
|
0: // Comment
|
||||||
|
)
|
||||||
)
|
)
|
||||||
2: Stmt_Case(
|
2: Stmt_Case(
|
||||||
cond: null
|
cond: null
|
||||||
|
@ -13,6 +13,7 @@ switch ($expr) {
|
|||||||
case 4:
|
case 4:
|
||||||
echo 'Third case, return instead of break';
|
echo 'Third case, return instead of break';
|
||||||
return;
|
return;
|
||||||
|
// Comment
|
||||||
default:
|
default:
|
||||||
echo 'Default case';
|
echo 'Default case';
|
||||||
break;
|
break;
|
||||||
@ -29,6 +30,7 @@ switch ($expr) {
|
|||||||
case 4:
|
case 4:
|
||||||
echo 'Third case, return instead of break';
|
echo 'Third case, return instead of break';
|
||||||
return;
|
return;
|
||||||
|
// Comment
|
||||||
default:
|
default:
|
||||||
echo 'Default case';
|
echo 'Default case';
|
||||||
break;
|
break;
|
||||||
|
@ -21,7 +21,7 @@ foreach (filesInDir($dir, 'test') as $fileName => $code) {
|
|||||||
foreach ($tests as list($modeLine, list($input, $expected))) {
|
foreach ($tests as list($modeLine, list($input, $expected))) {
|
||||||
$modes = null !== $modeLine ? array_fill_keys(explode(',', $modeLine), true) : [];
|
$modes = null !== $modeLine ? array_fill_keys(explode(',', $modeLine), true) : [];
|
||||||
list($parser5, $parser7) = $codeParsingTest->createParsers($modes);
|
list($parser5, $parser7) = $codeParsingTest->createParsers($modes);
|
||||||
$output = isset($modes['php5'])
|
list(, $output) = isset($modes['php5'])
|
||||||
? $codeParsingTest->getParseOutput($parser5, $input, $modes)
|
? $codeParsingTest->getParseOutput($parser5, $input, $modes)
|
||||||
: $codeParsingTest->getParseOutput($parser7, $input, $modes);
|
: $codeParsingTest->getParseOutput($parser7, $input, $modes);
|
||||||
$newTests[] = [$modeLine, [$input, $output]];
|
$newTests[] = [$modeLine, [$input, $output]];
|
||||||
|
Reference in New Issue
Block a user