mirror of
https://github.com/nikic/PHP-Parser.git
synced 2025-06-17 05:11:00 +02:00
Compare commits
300 Commits
Author | SHA1 | Date | |
---|---|---|---|
e4a54fa90a | |||
3125b54e5e | |||
7f8ff1b9a4 | |||
91a462ce76 | |||
610617fe93 | |||
ff2d85dc6b | |||
b998d1e9b5 | |||
6aba7624ed | |||
9c18e3db49 | |||
ae52aadb43 | |||
9cea94000a | |||
bb87e28e7d | |||
7484acb88b | |||
bc5ba47b28 | |||
b9996315a6 | |||
de3470190c | |||
1c3eabb000 | |||
8d1e86b47f | |||
ff10cc9d08 | |||
14454326e9 | |||
3a45c1a121 | |||
ad9c42b66a | |||
08215e7646 | |||
c18bb27723 | |||
dd0adcc96c | |||
e4505de346 | |||
a513ccabb7 | |||
d817818b5d | |||
6a273c9fbd | |||
c2d3ecad35 | |||
e57b3a0978 | |||
1cdb280a30 | |||
d01fafcb40 | |||
67df02c844 | |||
b85b6b3519 | |||
94c715d97e | |||
4dacbb8d39 | |||
aa685e711a | |||
edafeb85c4 | |||
68d07c4662 | |||
8fae99aafe | |||
c7ada124d0 | |||
f6617e6d25 | |||
2499534729 | |||
e0a2043089 | |||
e6e52abae7 | |||
7f72c84122 | |||
fc8ac71e76 | |||
a8968caa5b | |||
5285df8f22 | |||
4366aa2fb0 | |||
bf7d811cda | |||
248b29ecf6 | |||
bcb45d31eb | |||
3bc31488ce | |||
92b39e3d1f | |||
1c7fd314d1 | |||
fb8175567e | |||
63abf9cb3f | |||
ceb4932ca8 | |||
aa289c9694 | |||
579f4ce846 | |||
8d3cb5f57b | |||
de82a40d04 | |||
a86151f24f | |||
e6e8791848 | |||
83b958763f | |||
4dbb02c57b | |||
4fcdac40d1 | |||
04feb90d79 | |||
dc3ace55c3 | |||
e5453f0d46 | |||
b507fa43da | |||
336a49b428 | |||
fa174b093f | |||
94ca9a7ab9 | |||
0bb74e03aa | |||
8d3f48ab75 | |||
1c11626f0a | |||
05e2cd287e | |||
bac91b426e | |||
a75164c77e | |||
c59e75f873 | |||
4392a7b164 | |||
a659240dc2 | |||
68e9b91e9c | |||
7162b36f2d | |||
73be07672b | |||
e2c0c598a3 | |||
66f88cfa07 | |||
9b6a2577fa | |||
457fe049a8 | |||
6a2e1ae440 | |||
47c973b3aa | |||
56bc8ebb9b | |||
361398bc0d | |||
08131e7ff2 | |||
fd6e8d7ea8 | |||
0ba710affa | |||
72231abe6d | |||
7254040998 | |||
57bf378412 | |||
b58157f024 | |||
ab596db285 | |||
7f92edf3a1 | |||
837579a40c | |||
b241a121a3 | |||
5054a68bfb | |||
d16f050d74 | |||
5900d78cc9 | |||
776275361a | |||
651341d53b | |||
001f2d3f76 | |||
b0242d31f8 | |||
f5de7f9894 | |||
3193f7aae7 | |||
bbec051e51 | |||
f071b66013 | |||
57bc42517b | |||
bb2ac91115 | |||
e3888cbe02 | |||
310155832a | |||
ed8a744cd5 | |||
69aec6fb5b | |||
75880fbe2d | |||
65de924493 | |||
eaee6687e0 | |||
f5f3b0d49d | |||
f6cc85a796 | |||
cc600b67f6 | |||
d418bf3951 | |||
4b1d9667af | |||
a02990a39a | |||
403a7c5315 | |||
cc328a4c9c | |||
3d4621bbea | |||
d8f9173390 | |||
df334eacaa | |||
5cfdc2e0a7 | |||
5a9fbca54a | |||
4f8f773b9f | |||
69ed40e401 | |||
ec8692fb9e | |||
b1cd07a7b5 | |||
61e624bc9d | |||
31065389f1 | |||
829c6c3c71 | |||
e145102345 | |||
a1e8e1a30e | |||
efd39a67a2 | |||
d77e6cd6e9 | |||
fa12dc8a22 | |||
a10780ca0d | |||
9373a8e9f5 | |||
e2e99f269b | |||
7b36ca3b6c | |||
d97cc3d96e | |||
6ab69a7dc9 | |||
af12807451 | |||
9d599040b7 | |||
05e6725b7a | |||
ec535ea14e | |||
58bf92e4f7 | |||
a664b28248 | |||
edec3bb07a | |||
770c314dd1 | |||
94b5017e89 | |||
fe069d6de2 | |||
4d4896e553 | |||
d11ef05aaf | |||
6fa073879e | |||
5a3a1ec25c | |||
441e6e1d31 | |||
8bdcb47815 | |||
46e7fea72d | |||
4a7d011317 | |||
4fea054ee0 | |||
7f862ac21c | |||
0808939f81 | |||
19dde1363e | |||
5b43809b48 | |||
947574d3dc | |||
7bfc320bda | |||
7646b31907 | |||
bc0bff3f87 | |||
c28b8556f5 | |||
24d583d5c3 | |||
b2c6497d63 | |||
4e162bd0ea | |||
73dc35cbbd | |||
b1af3d1f7d | |||
92275bdfa8 | |||
56d57d472a | |||
3da86df48f | |||
5ec59eebe4 | |||
a32e3797d4 | |||
e5fbdd6bda | |||
3da189769c | |||
a6846e3b71 | |||
3b4abbfc97 | |||
7f6477ed83 | |||
56b810e91d | |||
6168abd9a0 | |||
888b9dcf30 | |||
e1ab92275c | |||
3c44785e82 | |||
8635365a30 | |||
ceaed32e94 | |||
fba61390d8 | |||
7bdb55f9a8 | |||
b35301659b | |||
60f1504978 | |||
aa75838a5f | |||
5f7070e94f | |||
8f40eec122 | |||
e4f493cd1d | |||
7419649eae | |||
6b6c903585 | |||
9dc93aafaa | |||
ba85da88a0 | |||
901b895c02 | |||
55be521ff8 | |||
c877c1a64f | |||
bc75ac2990 | |||
c3cbf07946 | |||
e15b6aa3e0 | |||
12034b19d7 | |||
369a078c54 | |||
deb64ab676 | |||
1ec5591574 | |||
291bb27f6a | |||
b5935a4aff | |||
9d680b24f0 | |||
510e0cd202 | |||
2beb4e7fd6 | |||
da97f78e25 | |||
f291a19fd5 | |||
dce34f37db | |||
9cd9c0cd52 | |||
9857a545e2 | |||
54b61ebe7c | |||
329e90c239 | |||
9f5ec5a69a | |||
4a434fdc1b | |||
f8a2f6e760 | |||
865bfb2acf | |||
f4ea0270c8 | |||
8f623fb241 | |||
3bbf8d8f7a | |||
90b6d7cb5b | |||
7fa5495d64 | |||
987c61e935 | |||
2b72bae3d9 | |||
2b6804aa50 | |||
d9911c8da5 | |||
79afd56565 | |||
5cc2750ebc | |||
e072c789d1 | |||
bfea338d36 | |||
e3b87f40aa | |||
70f86cb6cb | |||
13304d5991 | |||
7b441d2142 | |||
cb5dd28985 | |||
5e565e8046 | |||
b9b6aeeed9 | |||
0607450f78 | |||
aea3a9efe4 | |||
414adfb146 | |||
b5fb6f2d0a | |||
670ab2f178 | |||
48d3243abe | |||
d7f3c4f9d3 | |||
a79306ccd9 | |||
6238f5f9f9 | |||
67274b9594 | |||
11b44c15b5 | |||
7001116be5 | |||
ced914a3b7 | |||
7623d20f69 | |||
33552764ad | |||
953f8c9631 | |||
b0c962911e | |||
1bfbd7bcc8 | |||
065c720c28 | |||
5ff2519e1e | |||
0c9c8d58ab | |||
3b4dd387b8 | |||
61574a1818 | |||
be2ed243f6 | |||
f21309f52f | |||
f581318dd5 | |||
4d2a4d02b0 | |||
9b2d35d1ac | |||
0f582e1708 | |||
d32d937d47 | |||
f46c909b5a | |||
a947e731c3 | |||
122f449960 | |||
6bcc6c31dd |
16
.travis.yml
16
.travis.yml
@ -1,5 +1,5 @@
|
||||
language: php
|
||||
|
||||
dist: trusty
|
||||
sudo: false
|
||||
|
||||
cache:
|
||||
@ -7,14 +7,13 @@ cache:
|
||||
- $HOME/.composer/cache
|
||||
|
||||
php:
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7.0
|
||||
- 7.1
|
||||
- 7.2
|
||||
- nightly
|
||||
- hhvm
|
||||
|
||||
install:
|
||||
- if [ $TRAVIS_PHP_VERSION = '5.6' ]; then composer require satooshi/php-coveralls '~1.0'; fi
|
||||
- if [ $TRAVIS_PHP_VERSION = '7.0' ]; then composer require satooshi/php-coveralls '~1.0'; fi
|
||||
- composer install --prefer-dist
|
||||
|
||||
matrix:
|
||||
@ -23,9 +22,8 @@ matrix:
|
||||
fast_finish: true
|
||||
|
||||
script:
|
||||
- if [ $TRAVIS_PHP_VERSION = '5.6' ]; then vendor/bin/phpunit --coverage-clover build/logs/clover.xml; else vendor/bin/phpunit; fi
|
||||
- if [ $TRAVIS_PHP_VERSION = '7.0' ]; then test_old/run-php-src.sh; fi
|
||||
- if [ $TRAVIS_PHP_VERSION = '7.0' ]; then vendor/bin/phpunit --coverage-clover build/logs/clover.xml; else vendor/bin/phpunit; fi
|
||||
- if [ $TRAVIS_PHP_VERSION = '7.1' ]; then test_old/run-php-src.sh; fi
|
||||
|
||||
after_success:
|
||||
if [ $TRAVIS_PHP_VERSION = '5.6' ]; then php vendor/bin/coveralls; fi
|
||||
|
||||
- if [ $TRAVIS_PHP_VERSION = '7.0' ]; then php vendor/bin/coveralls; fi
|
||||
|
218
CHANGELOG.md
218
CHANGELOG.md
@ -1,8 +1,216 @@
|
||||
Version 3.0.6-dev
|
||||
Version 4.0.2-dev
|
||||
-----------------
|
||||
|
||||
Nothing yet.
|
||||
|
||||
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)
|
||||
--------------------------------
|
||||
|
||||
### Fixed
|
||||
|
||||
* In formatting-preserving pretty printer: Fixed indentation when inserting into lists. (#466)
|
||||
|
||||
### Added
|
||||
|
||||
* In formatting-preserving pretty printer: Improved formatting of elements inserted into multi-line
|
||||
arrays.
|
||||
|
||||
### Removed
|
||||
|
||||
* The `Autoloader` class has been removed. It is now required to use the Composer autoloader.
|
||||
|
||||
Version 4.0.0-alpha3 (2017-12-26)
|
||||
---------------------------------
|
||||
|
||||
### Fixed
|
||||
|
||||
* In the formatting-preserving pretty printer:
|
||||
* Fixed comment indentation.
|
||||
* Fixed handling of inline HTML in the fallback case.
|
||||
* Fixed insertion into list nodes that require creation of a code block.
|
||||
|
||||
### Added
|
||||
|
||||
* Added support for inserting at the start of list nodes in formatting-preserving pretty printer.
|
||||
|
||||
Version 4.0.0-alpha2 (2017-11-10)
|
||||
---------------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* In the formatting-preserving pretty printer:
|
||||
* Added support for changing modifiers.
|
||||
* Added support for anonymous classes.
|
||||
* Added support for removing from list nodes.
|
||||
* Improved support for changing comments.
|
||||
* Added start token offsets to comments.
|
||||
|
||||
Version 4.0.0-alpha1 (2017-10-18)
|
||||
---------------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* Added experimental support for format-preserving pretty-printing. In this mode formatting will be
|
||||
preserved for parts of the code which have not been modified.
|
||||
* Added `replaceNodes` option to `NameResolver`, defaulting to true. If this option is disabled,
|
||||
resolved names will be added as `resolvedName` attributes, instead of replacing the original
|
||||
names.
|
||||
* Added `NodeFinder` class, which can be used to find nodes based on a callback or class name. This
|
||||
is a utility to avoid custom node visitor implementations for simple search operations.
|
||||
* Added `ClassMethod::isMagic()` method.
|
||||
* Added `BuilderFactory` methods: `val()` method for creating an AST for a simple value, `concat()`
|
||||
for creating concatenation trees, `args()` for preparing function arguments.
|
||||
* Added `NameContext` class, which encapsulates the `NameResolver` logic independently of the actual
|
||||
AST traversal. This facilitates use in other context, such as class names in doc comments.
|
||||
Additionally it provides an API for getting the shortest representation of a name.
|
||||
* Added `Node::setAttributes()` method.
|
||||
* Added `JsonDecoder`. This allows conversion JSON back into an AST.
|
||||
* Added `Name` methods `toLowerString()` and `isSpecialClassName()`.
|
||||
* Added `Identifier` and `VarLikeIdentifier` nodes, which are used in place of simple strings in
|
||||
many places.
|
||||
* Added `getComments()`, `getStartLine()`, `getEndLine()`, `getStartTokenPos()`, `getEndTokenPos()`,
|
||||
`getStartFilePos()` and `getEndFilePos()` methods to `Node`. These provide a more obvious access
|
||||
point for the already existing attributes of the same name.
|
||||
* Added `ConstExprEvaluator` to evaluate constant expressions to PHP values.
|
||||
* Added `Expr\BinaryOp::getOperatorSigil()`, returning `+` for `Expr\BinaryOp\Plus`, etc.
|
||||
|
||||
### Changed
|
||||
|
||||
* Many subnodes that previously held simple strings now use `Identifier` (or `VarLikeIdentifier`)
|
||||
nodes. Please see the UPGRADE-4.0 file for an exhaustive list of affected nodes and some notes on
|
||||
possible impact.
|
||||
* Expression statements (`expr;`) are now represented using a `Stmt\Expression` node. Previously
|
||||
these statements were directly represented as their constituent expression.
|
||||
* The `name` subnode of `Param` has been renamed to `var` and now contains a `Variable` rather than
|
||||
a plain string.
|
||||
* The `name` subnode of `StaticVar` has been renamed to `var` and 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 `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
|
||||
can be used to get the effective alias, even if it is not explicitly given.
|
||||
|
||||
### Removed
|
||||
|
||||
* Support for running on PHP 5 and HHVM has been removed. You can however still parse code of old
|
||||
PHP versions (such as PHP 5.2), while running on PHP 7.
|
||||
* 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 `NodeTraverser` no longer accepts `false` as a return value from a `leaveNode()` method.
|
||||
`NodeTraverser::REMOVE_NODE` should be returned instead.
|
||||
* The `Node::setLine()` method has been removed. If you really need to, you can use `setAttribute()`
|
||||
instead.
|
||||
* The misspelled `Class_::VISIBILITY_MODIFER_MASK` constant has been dropped in favor of
|
||||
`Class_::VISIBILITY_MODIFIER_MASK`.
|
||||
* The XML serializer has been removed. As such, the classes `Serializer\XML`, and
|
||||
`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`.
|
||||
However, this is an internal class and should not be used directly.
|
||||
|
||||
Version 3.1.5 (2018-02-28)
|
||||
--------------------------
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed duplicate comment assignment in switch statements. (#469)
|
||||
* Improve compatibility with PHP-Scoper. (#477)
|
||||
|
||||
Version 3.1.4 (2018-01-25)
|
||||
--------------------------
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed pretty printing of `-(-$x)` and `+(+$x)`. (#459)
|
||||
|
||||
Version 3.1.3 (2017-12-26)
|
||||
--------------------------
|
||||
|
||||
### Fixed
|
||||
|
||||
* Improve compatibility with php-scoper, by supporting prefixed namespaces in
|
||||
`NodeAbstract::getType()`.
|
||||
|
||||
Version 3.1.2 (2017-11-04)
|
||||
--------------------------
|
||||
|
||||
### Fixed
|
||||
|
||||
* Comments on empty blocks are now preserved on a `Stmt\Nop` node. (#382)
|
||||
|
||||
### Added
|
||||
|
||||
* Added `kind` attribute for `Stmt\Namespace_` node, which is one of `KIND_SEMICOLON` or
|
||||
`KIND_BRACED`. (#417)
|
||||
* Added `setDocComment()` method to namespace builder. (#437)
|
||||
|
||||
Version 3.1.1 (2017-09-02)
|
||||
--------------------------
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed syntax error on comment after brace-style namespace declaration. (#412)
|
||||
* Added support for TraitUse statements in trait builder. (#413)
|
||||
|
||||
Version 3.1.0 (2017-07-28)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* [PHP 7.2] Added support for trailing comma in group use statements.
|
||||
* [PHP 7.2] Added support for `object` type. This means `object` types will now be represented as a
|
||||
builtin type (a simple `"object"` string), rather than a class `Name`.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Floating-point numbers are now printed correctly if the LC_NUMERIC locale uses a comma as decimal
|
||||
separator.
|
||||
|
||||
### Changed
|
||||
|
||||
* `Name::$parts` is no longer deprecated.
|
||||
|
||||
Version 3.0.6 (2017-06-28)
|
||||
--------------------------
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed the spelling of `Class_::VISIBILITY_MODIFIER_MASK`. The previous spelling of
|
||||
`Class_::VISIBILITY_MODIFER_MASK` is preserved for backwards compatibility.
|
||||
* The pretty printing will now preserve comments inside array literals and function calls by
|
||||
printing the array items / function arguments on separate lines. Array literals and functions that
|
||||
do not contain comments are not affected.
|
||||
|
||||
### Added
|
||||
|
||||
* Added `Builder\Param::makeVariadic()`.
|
||||
|
||||
### Deprecated
|
||||
|
||||
* The `Node::setLine()` method has been deprecated.
|
||||
|
||||
Version 3.0.5 (2017-03-05)
|
||||
--------------------------
|
||||
|
||||
@ -94,7 +302,7 @@ This release primarily improves our support for error recovery.
|
||||
`NameResolver::__construct()`.
|
||||
* The `NameResolver` now adds a `namespacedName` attribute on name nodes that cannot be statically
|
||||
resolved (unqualified unaliased function or constant names in namespaces).
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed attribute assignment for `GroupUse` prefix and variables in interpolated strings.
|
||||
@ -184,7 +392,7 @@ Additionally the following changes were made:
|
||||
takes an array of subnodes. Unlike classes/interfaces, traits can only have a `stmts` subnode.
|
||||
* The `NodeDumper` now prints class/method/property/constant modifiers, as well as the include and
|
||||
use type in a textual representation, instead of only showing the number.
|
||||
* All methods on `PrettyPrinter\Standard` are now protected. Previoulsy most of them were public.
|
||||
* All methods on `PrettyPrinter\Standard` are now protected. Previously most of them were public.
|
||||
|
||||
### Removed
|
||||
|
||||
@ -222,7 +430,7 @@ Version 2.1.0 (2016-04-19)
|
||||
* Added `kind` attribute to `Expr\Exit` to distinguish between `exit` and `die`.
|
||||
* Added `kind` attribute to `Scalar\LNumber` to distinguish between decimal, binary, octal and
|
||||
hexadecimal numbers.
|
||||
* Added `kind` attribtue to `Expr\Array` to distinguish between `array()` and `[]`.
|
||||
* Added `kind` attribute to `Expr\Array` to distinguish between `array()` and `[]`.
|
||||
* Added `kind` attribute to `Scalar\String` and `Scalar\Encapsed` to distinguish between
|
||||
single-quoted, double-quoted, heredoc and nowdoc string.
|
||||
* Added `docLabel` attribute to `Scalar\String` and `Scalar\Encapsed`, if it is a heredoc or
|
||||
@ -338,4 +546,4 @@ A more detailed description of backwards incompatible changes can be found in th
|
||||
|
||||
**This changelog only includes changes from the 2.0 series. For older changes see the
|
||||
[1.x series changelog](https://github.com/nikic/PHP-Parser/blob/1.x/CHANGELOG.md) and the
|
||||
[0.9 series changelog](https://github.com/nikic/PHP-Parser/blob/0.9/CHANGELOG.md).**
|
||||
[0.9 series changelog](https://github.com/nikic/PHP-Parser/blob/0.9/CHANGELOG.md).**
|
||||
|
4
LICENSE
4
LICENSE
@ -1,4 +1,4 @@
|
||||
Copyright (c) 2011 by Nikita Popov.
|
||||
Copyright (c) 2011-2018 by Nikita Popov.
|
||||
|
||||
Some rights reserved.
|
||||
|
||||
@ -28,4 +28,4 @@ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
236
README.md
236
README.md
@ -3,97 +3,223 @@ PHP Parser
|
||||
|
||||
[](https://travis-ci.org/nikic/PHP-Parser) [](https://coveralls.io/github/nikic/PHP-Parser?branch=master)
|
||||
|
||||
This is a PHP 5.2 to PHP 7.1 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.
|
||||
|
||||
[**Documentation for version 3.x**][doc_master] (stable; for running on PHP >= 5.5; for parsing PHP 5.2 to PHP 7.1).
|
||||
[**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 2.x][doc_2_x] (stable; for running on PHP >= 5.4; for parsing PHP 5.2 to PHP 7.0).
|
||||
[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 1.x][doc_1_x] (unsupported; for running on PHP >= 5.3; for parsing PHP 5.2 to PHP 5.6).
|
||||
Features
|
||||
--------
|
||||
|
||||
In a Nutshell
|
||||
-------------
|
||||
The main features provided by this library are:
|
||||
|
||||
The parser turns PHP source code into an abstract syntax tree. For example, if you pass the following code into the
|
||||
parser:
|
||||
* Parsing PHP 5 and PHP 7 code into an abstract syntax tree (AST).
|
||||
* Invalid code can be parsed into a partial AST.
|
||||
* The AST contains accurate location information.
|
||||
* Dumping the AST in human-readable form.
|
||||
* Converting an AST back to PHP code.
|
||||
* Experimental: Formatting can be preserved for partially changed ASTs.
|
||||
* Infrastructure to traverse and modify ASTs.
|
||||
* Resolution of namespaced names.
|
||||
* Evaluation of constant expressions.
|
||||
* Builders to simplify AST construction for code generation.
|
||||
* Converting an AST into JSON and back.
|
||||
|
||||
Quick Start
|
||||
-----------
|
||||
|
||||
Install the library using [composer](https://getcomposer.org):
|
||||
|
||||
php composer.phar require nikic/php-parser
|
||||
|
||||
Parse some PHP code into an AST and dump the result in human-readable form:
|
||||
|
||||
```php
|
||||
<?php
|
||||
echo 'Hi', 'World';
|
||||
hello\world('foo', 'bar' . 'baz');
|
||||
use PhpParser\Error;
|
||||
use PhpParser\NodeDumper;
|
||||
use PhpParser\ParserFactory;
|
||||
|
||||
$code = <<<'CODE'
|
||||
<?php
|
||||
|
||||
function test($foo)
|
||||
{
|
||||
var_dump($foo);
|
||||
}
|
||||
CODE;
|
||||
|
||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
try {
|
||||
$ast = $parser->parse($code);
|
||||
} catch (Error $error) {
|
||||
echo "Parse error: {$error->getMessage()}\n";
|
||||
return;
|
||||
}
|
||||
|
||||
$dumper = new NodeDumper;
|
||||
echo $dumper->dump($ast) . "\n";
|
||||
```
|
||||
|
||||
You'll get a syntax tree looking roughly like this:
|
||||
This dumps an AST looking something like this:
|
||||
|
||||
```php
|
||||
```
|
||||
array(
|
||||
0: Stmt_Echo(
|
||||
exprs: array(
|
||||
0: Scalar_String(
|
||||
value: Hi
|
||||
)
|
||||
1: Scalar_String(
|
||||
value: World
|
||||
0: Stmt_Function(
|
||||
byRef: false
|
||||
name: Identifier(
|
||||
name: test
|
||||
)
|
||||
params: array(
|
||||
0: Param(
|
||||
type: null
|
||||
byRef: false
|
||||
variadic: false
|
||||
var: Expr_Variable(
|
||||
name: foo
|
||||
)
|
||||
default: null
|
||||
)
|
||||
)
|
||||
)
|
||||
1: Expr_FuncCall(
|
||||
name: Name(
|
||||
parts: array(
|
||||
0: hello
|
||||
1: world
|
||||
)
|
||||
)
|
||||
args: array(
|
||||
0: Arg(
|
||||
value: Scalar_String(
|
||||
value: foo
|
||||
)
|
||||
byRef: false
|
||||
)
|
||||
1: Arg(
|
||||
value: Expr_Concat(
|
||||
left: Scalar_String(
|
||||
value: bar
|
||||
returnType: null
|
||||
stmts: array(
|
||||
0: Stmt_Expression(
|
||||
expr: Expr_FuncCall(
|
||||
name: Name(
|
||||
parts: array(
|
||||
0: var_dump
|
||||
)
|
||||
)
|
||||
right: Scalar_String(
|
||||
value: baz
|
||||
args: array(
|
||||
0: Arg(
|
||||
value: Expr_Variable(
|
||||
name: foo
|
||||
)
|
||||
byRef: false
|
||||
unpack: false
|
||||
)
|
||||
)
|
||||
)
|
||||
byRef: false
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
You can then work with this syntax tree, for example to statically analyze the code (e.g. to find
|
||||
programming errors or security issues).
|
||||
Let's traverse the AST and perform some kind of modification. For example, drop all function bodies:
|
||||
|
||||
Additionally, you can convert a syntax tree back to PHP code. This allows you to do code preprocessing
|
||||
(like automatedly porting code to older PHP versions).
|
||||
```php
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Function_;
|
||||
use PhpParser\NodeTraverser;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
|
||||
Installation
|
||||
------------
|
||||
$traverser = new NodeTraverser();
|
||||
$traverser->addVisitor(new class extends NodeVisitorAbstract {
|
||||
public function enterNode(Node $node) {
|
||||
if ($node instanceof Function_) {
|
||||
// Clean out the function body
|
||||
$node->stmts = [];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
The preferred installation method is [composer](https://getcomposer.org):
|
||||
$ast = $traverser->traverse($ast);
|
||||
echo $dumper->dump($ast) . "\n";
|
||||
```
|
||||
|
||||
php composer.phar require nikic/php-parser
|
||||
This gives us an AST where the `Function_::$stmts` are empty:
|
||||
|
||||
```
|
||||
array(
|
||||
0: Stmt_Function(
|
||||
byRef: false
|
||||
name: Identifier(
|
||||
name: test
|
||||
)
|
||||
params: array(
|
||||
0: Param(
|
||||
type: null
|
||||
byRef: false
|
||||
variadic: false
|
||||
var: Expr_Variable(
|
||||
name: foo
|
||||
)
|
||||
default: null
|
||||
)
|
||||
)
|
||||
returnType: null
|
||||
stmts: array(
|
||||
)
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
Finally, we can convert the new AST back to PHP code:
|
||||
|
||||
```php
|
||||
use PhpParser\PrettyPrinter;
|
||||
|
||||
$prettyPrinter = new PrettyPrinter\Standard;
|
||||
echo $prettyPrinter->prettyPrintFile($ast);
|
||||
```
|
||||
|
||||
This gives us our original code, minus the `var_dump()` call inside the function:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
function test($foo)
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
For a more comprehensive introduction, see the documentation.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
1. [Introduction](doc/0_Introduction.markdown)
|
||||
2. [Usage of basic components](doc/2_Usage_of_basic_components.markdown)
|
||||
3. [Other node tree representations](doc/3_Other_node_tree_representations.markdown)
|
||||
4. [Code generation](doc/4_Code_generation.markdown)
|
||||
|
||||
Component documentation:
|
||||
|
||||
1. [Error handling](doc/component/Error_handling.markdown)
|
||||
2. [Lexer](doc/component/Lexer.markdown)
|
||||
* [Walking the AST](doc/component/Walking_the_AST.markdown)
|
||||
* Node visitors
|
||||
* Modifying the AST from a visitor
|
||||
* Short-circuiting traversals
|
||||
* Interleaved visitors
|
||||
* Simple node finding API
|
||||
* Parent and sibling references
|
||||
* [Name resolution](doc/component/Name_resolution.markdown)
|
||||
* Name resolver options
|
||||
* Name resolution context
|
||||
* [Pretty printing](doc/component/Pretty_printing.markdown)
|
||||
* Converting AST back to PHP code
|
||||
* Customizing formatting
|
||||
* Formatting-preserving code transformations
|
||||
* [AST builders](component/AST_builders.markdown)
|
||||
* Fluent builders for AST nodes
|
||||
* [Lexer](doc/component/Lexer.markdown)
|
||||
* Lexer options
|
||||
* Token and file positions for nodes
|
||||
* Custom attributes
|
||||
* [Error handling](doc/component/Error_handling.markdown)
|
||||
* Column information for errors
|
||||
* Error recovery (parsing of syntactically incorrect code)
|
||||
* [Constant expression evaluation](component/Constant_expression_evaluation.markdown)
|
||||
* Evaluating constant/property/etc initializers
|
||||
* Handling errors and unsupported expressions
|
||||
* [JSON representation](component/JSON_representation.markdown)
|
||||
* JSON encoding and decoding of ASTs
|
||||
* [Performance](doc/component/Performance.markdown)
|
||||
* Disabling XDebug
|
||||
* Reusing objects
|
||||
* Garbage collection impact
|
||||
* [Frequently asked questions](component/FAQ.markdown)
|
||||
* Parent and sibling references
|
||||
|
||||
[doc_1_x]: https://github.com/nikic/PHP-Parser/tree/1.x/doc
|
||||
[doc_2_x]: https://github.com/nikic/PHP-Parser/tree/2.x/doc
|
||||
[doc_3_x]: https://github.com/nikic/PHP-Parser/tree/3.x/doc
|
||||
[doc_master]: https://github.com/nikic/PHP-Parser/tree/master/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 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,
|
||||
if you used the error recovery mode or have a custom lexer implementation.
|
||||
* A number of deprecated methods were removed.
|
||||
@ -115,7 +115,7 @@ public function startLexing($code);
|
||||
public function startLexing($code, ErrorHandler $errorHandler = null);
|
||||
```
|
||||
|
||||
If you use a custom lexer with overriden `startLexing()` method, it needs to be changed to accept
|
||||
If you use a custom lexer with overridden `startLexing()` method, it needs to be changed to accept
|
||||
the extra parameter. The value should be passed on to the parent method.
|
||||
|
||||
#### Error checks in node constructors
|
||||
@ -148,7 +148,7 @@ The following methods, arguments or options have been removed:
|
||||
namespace into fully qualified names. For example `foo()` in the global namespace resolves to
|
||||
`\foo()`. For names where no static resolution is possible, a `namespacedName` attribute is
|
||||
added now, containing the namespaced variant of the name.
|
||||
* All methods on `PrettyPrinter\Standard` are now protected. Previoulsy most of them were public.
|
||||
* All methods on `PrettyPrinter\Standard` are now protected. Previously most of them were public.
|
||||
The pretty printer should only be invoked using the `prettyPrint()`, `prettyPrintFile()` and
|
||||
`prettyPrintExpr()` methods.
|
||||
* The node dumper now prints numeric values that act as enums/flags in a string representation.
|
||||
|
77
UPGRADE-4.0.md
Normal file
77
UPGRADE-4.0.md
Normal file
@ -0,0 +1,77 @@
|
||||
Upgrading from PHP-Parser 3.x to 4.0
|
||||
====================================
|
||||
|
||||
### PHP version requirements
|
||||
|
||||
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.
|
||||
|
||||
HHVM is no longer actively supported.
|
||||
|
||||
### Changes to the node structure
|
||||
|
||||
* Many subnodes that previously held simple strings now store `Identifier` nodes instead (or
|
||||
`VarLikeIdentifier` nodes if they have form `$ident`). The constructors of the affected nodes will
|
||||
automatically convert strings to `Identifier`s and `Identifier`s implement `__toString()`. As such
|
||||
some code continues to work without changes, but anything using `is_string()`, type-strict
|
||||
comparisons or strict-mode may require adjustment. The following is an exhaustive list of all
|
||||
affected subnodes:
|
||||
|
||||
* `Const::$name`
|
||||
* `NullableType::$type` (for simple types)
|
||||
* `Param::$type` (for simple types)
|
||||
* `Expr\ClassConstFetch::$name`
|
||||
* `Expr\Closure::$returnType` (for simple types)
|
||||
* `Expr\MethodCall::$name`
|
||||
* `Expr\PropertyFetch::$name`
|
||||
* `Expr\StaticCall::$name`
|
||||
* `Expr\StaticPropertyFetch::$name` (uses `VarLikeIdentifier`)
|
||||
* `Stmt\Class_::$name`
|
||||
* `Stmt\ClassMethod::$name`
|
||||
* `Stmt\ClassMethod::$returnType` (for simple types)
|
||||
* `Stmt\Function::$name`
|
||||
* `Stmt\Function::$returnType` (for simple types)
|
||||
* `Stmt\Goto_::$name`
|
||||
* `Stmt\Interface_::$name`
|
||||
* `Stmt\Label::$name`
|
||||
* `Stmt\PropertyProperty::$name` (uses `VarLikeIdentifier`)
|
||||
* `Stmt\TraitUseAdaptation\Alias::$method`
|
||||
* `Stmt\TraitUseAdaptation\Alias::$newName`
|
||||
* `Stmt\TraitUseAdaptation\Precedence::$method`
|
||||
* `Stmt\Trait_::$name`
|
||||
* `Stmt\UseUse::$alias`
|
||||
|
||||
* Expression statements (`expr;`) are now represented using a `Stmt\Expression` node. Previously
|
||||
these statements were directly represented as their constituent expression.
|
||||
* The `name` subnode of `Param` has been renamed to `var` and now contains a `Variable` rather than
|
||||
a plain string.
|
||||
* The `name` subnode of `StaticVar` has been renamed to `var` and 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 `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
|
||||
can be used to get the effective alias, even if it is not explicitly given.
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
* The indentation handling in the pretty printer has been changed (this is only relevant if you
|
||||
extend the pretty printer). Previously indentation was automatic, and parts were excluded using
|
||||
`pNoindent()`. Now no-indent is the default and newlines that require indentation should use
|
||||
`$this->nl`.
|
||||
|
||||
### Removed functionality
|
||||
|
||||
* 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 `NodeTraverser` no longer accepts `false` as a return value from a `leaveNode()` method.
|
||||
`NodeTraverser::REMOVE_NODE` should be returned instead.
|
||||
* The `Node::setLine()` method has been removed. If you really need to, you can use `setAttribute()`
|
||||
instead.
|
||||
* The misspelled `Class_::VISIBILITY_MODIFER_MASK` constant has been dropped in favor of
|
||||
`Class_::VISIBILITY_MODIFIER_MASK`.
|
||||
* The XML serializer has been removed. As such, the classes `Serializer\XML`, and
|
||||
`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`.
|
||||
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.
|
@ -26,9 +26,9 @@ if (empty($files)) {
|
||||
showHelp("Must specify at least one file.");
|
||||
}
|
||||
|
||||
$lexer = new PhpParser\Lexer\Emulative(array('usedAttributes' => array(
|
||||
$lexer = new PhpParser\Lexer\Emulative(['usedAttributes' => [
|
||||
'startLine', 'endLine', 'startFilePos', 'endFilePos', 'comments'
|
||||
)));
|
||||
]]);
|
||||
$parser = (new PhpParser\ParserFactory)->create(
|
||||
PhpParser\ParserFactory::PREFER_PHP7,
|
||||
$lexer
|
||||
@ -38,7 +38,6 @@ $dumper = new PhpParser\NodeDumper([
|
||||
'dumpPositions' => $attributes['with-positions'],
|
||||
]);
|
||||
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
|
||||
$serializer = new PhpParser\Serializer\XML;
|
||||
|
||||
$traverser = new PhpParser\NodeTraverser();
|
||||
$traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver);
|
||||
@ -82,9 +81,9 @@ foreach ($files as $file) {
|
||||
} elseif ('pretty-print' === $operation) {
|
||||
echo "==> Pretty print:\n";
|
||||
echo $prettyPrinter->prettyPrintFile($stmts), "\n";
|
||||
} elseif ('serialize-xml' === $operation) {
|
||||
echo "==> Serialized XML:\n";
|
||||
echo $serializer->serialize($stmts), "\n";
|
||||
} elseif ('json-dump' === $operation) {
|
||||
echo "==> JSON dump:\n";
|
||||
echo json_encode($stmts, JSON_PRETTY_PRINT), "\n";
|
||||
} elseif ('var-dump' === $operation) {
|
||||
echo "==> var_dump():\n";
|
||||
var_dump($stmts);
|
||||
@ -116,7 +115,7 @@ Operations is a list of the following options (--dump by default):
|
||||
|
||||
-d, --dump Dump nodes using NodeDumper
|
||||
-p, --pretty-print Pretty print file using PrettyPrinter\Standard
|
||||
--serialize-xml Serialize nodes using Serializer\XML
|
||||
-j, --json-dump Print json_encode() result
|
||||
--var-dump var_dump() nodes (for exact structure)
|
||||
-N, --resolve-names Resolve names using NodeVisitor\NameResolver
|
||||
-c, --with-column-info Show column-numbers for errors (if available)
|
||||
@ -135,13 +134,13 @@ OUTPUT
|
||||
}
|
||||
|
||||
function parseArgs($args) {
|
||||
$operations = array();
|
||||
$files = array();
|
||||
$attributes = array(
|
||||
$operations = [];
|
||||
$files = [];
|
||||
$attributes = [
|
||||
'with-column-info' => false,
|
||||
'with-positions' => false,
|
||||
'with-recovery' => false,
|
||||
);
|
||||
];
|
||||
|
||||
array_shift($args);
|
||||
$parseOptions = true;
|
||||
@ -160,8 +159,9 @@ function parseArgs($args) {
|
||||
case '-p':
|
||||
$operations[] = 'pretty-print';
|
||||
break;
|
||||
case '--serialize-xml':
|
||||
$operations[] = 'serialize-xml';
|
||||
case '--json-dump':
|
||||
case '-j':
|
||||
$operations[] = 'json-dump';
|
||||
break;
|
||||
case '--var-dump':
|
||||
$operations[] = 'var-dump';
|
||||
@ -198,5 +198,5 @@ function parseArgs($args) {
|
||||
}
|
||||
}
|
||||
|
||||
return array($operations, $files, $attributes);
|
||||
return [$operations, $files, $attributes];
|
||||
}
|
||||
|
@ -10,11 +10,11 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.5",
|
||||
"php": ">=7.0",
|
||||
"ext-tokenizer": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.0|~5.0"
|
||||
"phpunit/phpunit": "^6.5 || ^7.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@ -24,7 +24,7 @@
|
||||
"bin": ["bin/php-parse"],
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.0-dev"
|
||||
"dev-master": "4.0-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
Introduction
|
||||
============
|
||||
|
||||
This project is a PHP 5.2 to PHP 7.1 parser **written in PHP itself**.
|
||||
This project is a PHP 5.2 to PHP 7.2 parser **written in PHP itself**.
|
||||
|
||||
What is this for?
|
||||
-----------------
|
||||
@ -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
|
||||
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.
|
||||
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
|
||||
all the different syntaxes from a stream of tokens.
|
||||
|
||||
@ -26,17 +26,17 @@ programmatic PHP code analysis are incidentally PHP developers, not C developers
|
||||
What can it parse?
|
||||
------------------
|
||||
|
||||
The parser supports parsing PHP 5.2-5.6 and PHP 7.
|
||||
The parser supports parsing PHP 5.2-7.2.
|
||||
|
||||
As the parser is based on the tokens returned by `token_get_all` (which is only able to lex the PHP
|
||||
version it runs on), additionally a wrapper for emulating tokens from newer versions is provided.
|
||||
This allows to parse PHP 7.1 source code running on PHP 5.5, for example. This emulation is somewhat
|
||||
This allows to parse PHP 7.2 source code running on PHP 5.5, for example. This emulation is somewhat
|
||||
hacky and not perfect, but it should work well on any sane code.
|
||||
|
||||
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
|
||||
roughly looking like this:
|
||||
|
||||
@ -69,7 +69,7 @@ Apart from the parser itself this package also bundles support for some other, r
|
||||
* Support for pretty printing, which is the act of converting an AST into PHP code. Please note
|
||||
that "pretty printing" does not imply that the output is especially pretty. It's just how it's
|
||||
called ;)
|
||||
* Support for serializing and unserializing the node tree to XML
|
||||
* Support for serializing and unserializing the node tree to JSON
|
||||
* Support for dumping the node tree in a human readable form (see the section above for an
|
||||
example of how the output looks like)
|
||||
* Infrastructure for traversing and changing the AST (node traverser and node visitors)
|
||||
|
@ -41,7 +41,7 @@ Kind | Behavior
|
||||
`ParserFactory::ONLY_PHP7` | Parse code as PHP 7.
|
||||
`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
|
||||
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:
|
||||
|
||||
```php
|
||||
<?php
|
||||
use PhpParser\Error;
|
||||
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);
|
||||
|
||||
try {
|
||||
@ -66,27 +74,68 @@ try {
|
||||
|
||||
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
|
||||
generate a node tree looking like this:
|
||||
To dump the abstact syntax tree in human readable form, a `NodeDumper` can be used:
|
||||
|
||||
```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(
|
||||
0: Stmt_Echo(
|
||||
exprs: array(
|
||||
0: Scalar_String(
|
||||
value: Hi
|
||||
0: Stmt_Function(
|
||||
byRef: false
|
||||
name: Identifier(
|
||||
name: printLine
|
||||
)
|
||||
params: array(
|
||||
0: Param(
|
||||
type: null
|
||||
byRef: false
|
||||
variadic: false
|
||||
var: Expr_Variable(
|
||||
name: msg
|
||||
)
|
||||
default: null
|
||||
)
|
||||
1: Expr_FuncCall(
|
||||
name: Name(
|
||||
parts: array(
|
||||
0: hi
|
||||
1: getTarget
|
||||
)
|
||||
returnType: null
|
||||
stmts: array(
|
||||
0: Stmt_Echo(
|
||||
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
|
||||
`PhpParser\Node\Stmt\Echo_`.
|
||||
You can also use the `php-parse` script to obtain such a node dump by calling it either with a file
|
||||
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:
|
||||
|
||||
* `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`)
|
||||
and call arguments (`PhpParser\Node\Arg`).
|
||||
|
||||
Some node class names have a trailing `_`. This is used whenever the class name would otherwise clash
|
||||
with a PHP keyword.
|
||||
The `Node\Stmt\Expression` node is somewhat confusing in that it contains both the terms "statement"
|
||||
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
|
||||
`$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:
|
||||
|
||||
<?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
|
||||
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
|
||||
and handle inline HTML as the first/last statement more gracefully.
|
||||
|
||||
> Read more: [Pretty printing documentation](component/Pretty_printing.markdown)
|
||||
|
||||
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`
|
||||
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
|
||||
-----------------------------
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
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
|
||||
know which function they are referring to. In most cases this is a non-issue as the global functions
|
||||
are meant.
|
||||
@ -300,6 +374,8 @@ are meant.
|
||||
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`.
|
||||
|
||||
> Read more: [Name resolution documentation](component/Name_resolution.markdown)
|
||||
|
||||
Example: Converting namespaced code to pseudo namespaces
|
||||
--------------------------------------------------------
|
||||
|
||||
@ -365,7 +441,7 @@ class NamespaceConverter extends \PhpParser\NodeVisitorAbstract
|
||||
{
|
||||
public function leaveNode(Node $node) {
|
||||
if ($node instanceof Node\Name) {
|
||||
return new Node\Name($node->toString('_'));
|
||||
return new Node\Name(str_replace('\\', '_', $node->toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -373,7 +449,7 @@ class NamespaceConverter extends \PhpParser\NodeVisitorAbstract
|
||||
|
||||
The above code profits from the fact that the `NameResolver` already resolved all names as far as
|
||||
possible, so we don't need to do that. We only need to create a string with the name parts separated
|
||||
by underscores instead of backslashes. This is what `$node->toString('_')` does. (If you want to
|
||||
by underscores instead of backslashes. This is what `str_replace('\\', '_', $node->toString())` does. (If you want to
|
||||
create a name with backslashes either write `$node->toString()` or `(string) $node`.) Then we create
|
||||
a new name from the string and return it. Returning a new node replaces the old node.
|
||||
|
||||
@ -389,14 +465,14 @@ class NodeVisitor_NamespaceConverter extends \PhpParser\NodeVisitorAbstract
|
||||
{
|
||||
public function leaveNode(Node $node) {
|
||||
if ($node instanceof Node\Name) {
|
||||
return new Node\Name($node->toString('_'));
|
||||
return new Node\Name(str_replace('\\', '_', $node->toString()));
|
||||
} elseif ($node instanceof Stmt\Class_
|
||||
|| $node instanceof Stmt\Interface_
|
||||
|| $node instanceof Stmt\Function_) {
|
||||
$node->name = $node->namespacedName->toString('_');
|
||||
$node->name = str_replace('\\', '_', $node->namespacedName->toString());
|
||||
} elseif ($node instanceof Stmt\Const_) {
|
||||
foreach ($node->consts as $const) {
|
||||
$const->name = $const->namespacedName->toString('_');
|
||||
$const->name = str_replace('\\', '_', $const->namespacedName->toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -410,26 +486,27 @@ The last thing we need to do is remove the `namespace` and `use` statements:
|
||||
```php
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\NodeTraverser;
|
||||
|
||||
class NodeVisitor_NamespaceConverter extends \PhpParser\NodeVisitorAbstract
|
||||
{
|
||||
public function leaveNode(Node $node) {
|
||||
if ($node instanceof Node\Name) {
|
||||
return new Node\Name($node->toString('_'));
|
||||
return new Node\Name(str_replace('\\', '_', $node->toString()));
|
||||
} elseif ($node instanceof Stmt\Class_
|
||||
|| $node instanceof Stmt\Interface_
|
||||
|| $node instanceof Stmt\Function_) {
|
||||
$node->name = $node->namespacedName->toString('_');
|
||||
$node->name = str_replace('\\', '_', $node->namespacedName->toString();
|
||||
} elseif ($node instanceof Stmt\Const_) {
|
||||
foreach ($node->consts as $const) {
|
||||
$const->name = $const->namespacedName->toString('_');
|
||||
$const->name = str_replace('\\', '_', $const->namespacedName->toString());
|
||||
}
|
||||
} elseif ($node instanceof Stmt\Namespace_) {
|
||||
// returning an array merges is into the parent array
|
||||
return $node->stmts;
|
||||
} elseif ($node instanceof Stmt\Use_) {
|
||||
// returning false removed the node altogether
|
||||
return false;
|
||||
// remove use nodes altogether
|
||||
return NodeTraverser::REMOVE_NODE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,330 +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);
|
||||
$nodeDumper = new PhpParser\NodeDumper;
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
There is currently no mechanism to convert JSON back into a node tree. Furthermore, not all ASTs
|
||||
can be JSON encoded. In particular, JSON only supports UTF-8 strings.
|
||||
|
||||
Serialization to XML
|
||||
--------------------
|
||||
|
||||
It is also possible to serialize the node tree to XML using `PhpParser\Serializer\XML->serialize()`
|
||||
and to unserialize it using `PhpParser\Unserializer\XML->unserialize()`. This is useful for
|
||||
interfacing with other languages and applications or for doing transformation using XSLT.
|
||||
|
||||
```php
|
||||
<?php
|
||||
$code = <<<'CODE'
|
||||
<?php
|
||||
|
||||
function printLine($msg) {
|
||||
echo $msg, "\n";
|
||||
}
|
||||
|
||||
printLine('Hello World!!!');
|
||||
CODE;
|
||||
|
||||
$parser = (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::PREFER_PHP7);
|
||||
$serializer = new PhpParser\Serializer\XML;
|
||||
|
||||
try {
|
||||
$stmts = $parser->parse($code);
|
||||
|
||||
echo $serializer->serialize($stmts);
|
||||
} catch (PhpParser\Error $e) {
|
||||
echo 'Parse Error: ', $e->getMessage();
|
||||
}
|
||||
```
|
||||
|
||||
Produces:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<AST xmlns:node="http://nikic.github.com/PHPParser/XML/node" xmlns:subNode="http://nikic.github.com/PHPParser/XML/subNode" xmlns:scalar="http://nikic.github.com/PHPParser/XML/scalar">
|
||||
<scalar:array>
|
||||
<node:Stmt_Function line="2">
|
||||
<subNode:byRef>
|
||||
<scalar:false/>
|
||||
</subNode:byRef>
|
||||
<subNode:params>
|
||||
<scalar:array>
|
||||
<node:Param line="2">
|
||||
<subNode:name>
|
||||
<scalar:string>msg</scalar:string>
|
||||
</subNode:name>
|
||||
<subNode:default>
|
||||
<scalar:null/>
|
||||
</subNode:default>
|
||||
<subNode:type>
|
||||
<scalar:null/>
|
||||
</subNode:type>
|
||||
<subNode:byRef>
|
||||
<scalar:false/>
|
||||
</subNode:byRef>
|
||||
</node:Param>
|
||||
</scalar:array>
|
||||
</subNode:params>
|
||||
<subNode:stmts>
|
||||
<scalar:array>
|
||||
<node:Stmt_Echo line="3">
|
||||
<subNode:exprs>
|
||||
<scalar:array>
|
||||
<node:Expr_Variable line="3">
|
||||
<subNode:name>
|
||||
<scalar:string>msg</scalar:string>
|
||||
</subNode:name>
|
||||
</node:Expr_Variable>
|
||||
<node:Scalar_String line="3">
|
||||
<subNode:value>
|
||||
<scalar:string>
|
||||
</scalar:string>
|
||||
</subNode:value>
|
||||
</node:Scalar_String>
|
||||
</scalar:array>
|
||||
</subNode:exprs>
|
||||
</node:Stmt_Echo>
|
||||
</scalar:array>
|
||||
</subNode:stmts>
|
||||
<subNode:name>
|
||||
<scalar:string>printLine</scalar:string>
|
||||
</subNode:name>
|
||||
</node:Stmt_Function>
|
||||
<node:Expr_FuncCall line="6">
|
||||
<subNode:name>
|
||||
<node:Name line="6">
|
||||
<subNode:parts>
|
||||
<scalar:array>
|
||||
<scalar:string>printLine</scalar:string>
|
||||
</scalar:array>
|
||||
</subNode:parts>
|
||||
</node:Name>
|
||||
</subNode:name>
|
||||
<subNode:args>
|
||||
<scalar:array>
|
||||
<node:Arg line="6">
|
||||
<subNode:value>
|
||||
<node:Scalar_String line="6">
|
||||
<subNode:value>
|
||||
<scalar:string>Hello World!!!</scalar:string>
|
||||
</subNode:value>
|
||||
</node:Scalar_String>
|
||||
</subNode:value>
|
||||
<subNode:byRef>
|
||||
<scalar:false/>
|
||||
</subNode:byRef>
|
||||
</node:Arg>
|
||||
</scalar:array>
|
||||
</subNode:args>
|
||||
</node:Expr_FuncCall>
|
||||
</scalar:array>
|
||||
</AST>
|
||||
```
|
@ -1,84 +0,0 @@
|
||||
Code generation
|
||||
===============
|
||||
|
||||
It is also possible to generate code using the parser, by first creating an Abstract Syntax Tree and then using the
|
||||
pretty printer to convert it to PHP code. To simplify code generation, the project comes with builders which allow
|
||||
creating node trees using a fluid interface, instead of instantiating all nodes manually. Builders are available for
|
||||
the following syntactic elements:
|
||||
|
||||
* namespaces and use statements
|
||||
* classes, interfaces and traits
|
||||
* methods, functions and parameters
|
||||
* properties
|
||||
|
||||
Here is an example:
|
||||
|
||||
```php
|
||||
use PhpParser\BuilderFactory;
|
||||
use PhpParser\PrettyPrinter;
|
||||
use PhpParser\Node;
|
||||
|
||||
$factory = new BuilderFactory;
|
||||
$node = $factory->namespace('Name\Space')
|
||||
->addStmt($factory->use('Some\Other\Thingy')->as('SomeOtherClass'))
|
||||
->addStmt($factory->class('SomeClass')
|
||||
->extend('SomeOtherClass')
|
||||
->implement('A\Few', '\Interfaces')
|
||||
->makeAbstract() // ->makeFinal()
|
||||
|
||||
->addStmt($factory->method('someMethod')
|
||||
->makePublic()
|
||||
->makeAbstract() // ->makeFinal()
|
||||
->setReturnType('bool')
|
||||
->addParam($factory->param('someParam')->setTypeHint('SomeClass'))
|
||||
->setDocComment('/**
|
||||
* This method does something.
|
||||
*
|
||||
* @param SomeClass And takes a parameter
|
||||
*/')
|
||||
)
|
||||
|
||||
->addStmt($factory->method('anotherMethod')
|
||||
->makeProtected() // ->makePublic() [default], ->makePrivate()
|
||||
->addParam($factory->param('someParam')->setDefault('test'))
|
||||
// it is possible to add manually created nodes
|
||||
->addStmt(new Node\Expr\Print_(new Node\Expr\Variable('someParam')))
|
||||
)
|
||||
|
||||
// properties will be correctly reordered above the methods
|
||||
->addStmt($factory->property('someProperty')->makeProtected())
|
||||
->addStmt($factory->property('anotherProperty')->makePrivate()->setDefault(array(1, 2, 3)))
|
||||
)
|
||||
|
||||
->getNode()
|
||||
;
|
||||
|
||||
$stmts = array($node);
|
||||
$prettyPrinter = new PrettyPrinter\Standard();
|
||||
echo $prettyPrinter->prettyPrintFile($stmts);
|
||||
```
|
||||
|
||||
This will produce the following output with the standard pretty printer:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace Name\Space;
|
||||
|
||||
use Some\Other\Thingy as SomeClass;
|
||||
abstract class SomeClass extends SomeOtherClass implements A\Few, \Interfaces
|
||||
{
|
||||
protected $someProperty;
|
||||
private $anotherProperty = array(1, 2, 3);
|
||||
/**
|
||||
* This method does something.
|
||||
*
|
||||
* @param SomeClass And takes a parameter
|
||||
*/
|
||||
public abstract function someMethod(SomeClass $someParam) : bool;
|
||||
protected function anotherMethod($someParam = 'test')
|
||||
{
|
||||
print $someParam;
|
||||
}
|
||||
}
|
||||
```
|
46
doc/README.md
Normal file
46
doc/README.md
Normal file
@ -0,0 +1,46 @@
|
||||
Table of Contents
|
||||
=================
|
||||
|
||||
Guide
|
||||
-----
|
||||
|
||||
1. [Introduction](0_Introduction.markdown)
|
||||
2. [Usage of basic components](2_Usage_of_basic_components.markdown)
|
||||
|
||||
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 resolver options
|
||||
* Name resolution context
|
||||
* [Pretty printing](component/Pretty_printing.markdown)
|
||||
* Converting AST back to PHP code
|
||||
* Customizing formatting
|
||||
* Formatting-preserving code transformations
|
||||
* [AST builders](component/AST_builders.markdown)
|
||||
* Fluent builders for AST nodes
|
||||
* [Lexer](component/Lexer.markdown)
|
||||
* Lexer options
|
||||
* Token and file positions for nodes
|
||||
* Custom attributes
|
||||
* [Error handling](component/Error_handling.markdown)
|
||||
* Column information for errors
|
||||
* Error recovery (parsing of syntactically incorrect code)
|
||||
* [Constant expression evaluation](component/Constant_expression_evaluation.markdown)
|
||||
* Evaluating constant/property/etc initializers
|
||||
* Handling errors and unsupported expressions
|
||||
* [JSON representation](component/JSON_representation.markdown)
|
||||
* JSON encoding and decoding of ASTs
|
||||
* [Performance](component/Performance.markdown)
|
||||
* Disabling XDebug
|
||||
* Reusing objects
|
||||
* Garbage collection impact
|
||||
* [Frequently asked questions](component/FAQ.markdown)
|
||||
* Parent and sibling references
|
117
doc/component/AST_builders.markdown
Normal file
117
doc/component/AST_builders.markdown
Normal file
@ -0,0 +1,117 @@
|
||||
AST builders
|
||||
============
|
||||
|
||||
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
|
||||
be tedious to manually construct AST nodes. The project provides a number of utilities to simplify
|
||||
the construction of common AST nodes.
|
||||
|
||||
Fluent builders
|
||||
---------------
|
||||
|
||||
The library comes with a number of builders, which allow creating node trees using a fluent
|
||||
interface. Builders are created using the `BuilderFactory` and the final constructed node is
|
||||
accessed through `getNode()`. Fluent builders are available for
|
||||
the following syntactic elements:
|
||||
|
||||
* namespaces and use statements
|
||||
* classes, interfaces and traits
|
||||
* methods, functions and parameters
|
||||
* properties
|
||||
|
||||
Here is an example:
|
||||
|
||||
```php
|
||||
use PhpParser\BuilderFactory;
|
||||
use PhpParser\PrettyPrinter;
|
||||
use PhpParser\Node;
|
||||
|
||||
$factory = new BuilderFactory;
|
||||
$node = $factory->namespace('Name\Space')
|
||||
->addStmt($factory->use('Some\Other\Thingy')->as('SomeOtherClass'))
|
||||
->addStmt($factory->class('SomeOtherClass')
|
||||
->extend('SomeClass')
|
||||
->implement('A\Few', '\Interfaces')
|
||||
->makeAbstract() // ->makeFinal()
|
||||
|
||||
->addStmt($factory->method('someMethod')
|
||||
->makePublic()
|
||||
->makeAbstract() // ->makeFinal()
|
||||
->setReturnType('bool')
|
||||
->addParam($factory->param('someParam')->setTypeHint('SomeClass'))
|
||||
->setDocComment('/**
|
||||
* This method does something.
|
||||
*
|
||||
* @param SomeClass And takes a parameter
|
||||
*/')
|
||||
)
|
||||
|
||||
->addStmt($factory->method('anotherMethod')
|
||||
->makeProtected() // ->makePublic() [default], ->makePrivate()
|
||||
->addParam($factory->param('someParam')->setDefault('test'))
|
||||
// it is possible to add manually created nodes
|
||||
->addStmt(new Node\Expr\Print_(new Node\Expr\Variable('someParam')))
|
||||
)
|
||||
|
||||
// properties will be correctly reordered above the methods
|
||||
->addStmt($factory->property('someProperty')->makeProtected())
|
||||
->addStmt($factory->property('anotherProperty')->makePrivate()->setDefault(array(1, 2, 3)))
|
||||
)
|
||||
|
||||
->getNode()
|
||||
;
|
||||
|
||||
$stmts = array($node);
|
||||
$prettyPrinter = new PrettyPrinter\Standard();
|
||||
echo $prettyPrinter->prettyPrintFile($stmts);
|
||||
```
|
||||
|
||||
This will produce the following output with the standard pretty printer:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace Name\Space;
|
||||
|
||||
use Some\Other\Thingy as SomeClass;
|
||||
abstract class SomeOtherClass extends SomeClass implements A\Few, \Interfaces
|
||||
{
|
||||
protected $someProperty;
|
||||
private $anotherProperty = array(1, 2, 3);
|
||||
/**
|
||||
* This method does something.
|
||||
*
|
||||
* @param SomeClass And takes a parameter
|
||||
*/
|
||||
public abstract function someMethod(SomeClass $someParam) : bool;
|
||||
protected function anotherMethod($someParam = 'test')
|
||||
{
|
||||
print $someParam;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Additional helper methods
|
||||
-------------------------
|
||||
|
||||
The `BuilderFactory` also provides a number of additional helper methods, which directly return
|
||||
nodes. The following methods are currently available:
|
||||
|
||||
* `val($value)`: Creates an AST node for a literal value like `42` or `[1, 2, 3]`.
|
||||
* `args(array $args)`: Creates an array of function/method arguments, including the required `Arg`
|
||||
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.
|
||||
* `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
|
||||
operation is missing.
|
115
doc/component/Constant_expression_evaluation.markdown
Normal file
115
doc/component/Constant_expression_evaluation.markdown
Normal file
@ -0,0 +1,115 @@
|
||||
Constant expression evaluation
|
||||
==============================
|
||||
|
||||
Initializers for constants, properties, parameters, etc. have limited support for expressions. For
|
||||
example:
|
||||
|
||||
```php
|
||||
<?php
|
||||
class Test {
|
||||
const SECONDS_IN_HOUR = 60 * 60;
|
||||
const SECONDS_IN_DAY = 24 * self::SECONDS_IN_HOUR;
|
||||
}
|
||||
```
|
||||
|
||||
PHP-Parser supports evaluation of such constant expressions through the `ConstExprEvaluator` class:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use PhpParser\{ConstExprEvaluator, ConstExprEvaluationException};
|
||||
|
||||
$evalutator = new ConstExprEvaluator();
|
||||
try {
|
||||
$value = $evalutator->evaluateSilently($someExpr);
|
||||
} catch (ConstExprEvaluationException $e) {
|
||||
// Either the expression contains unsupported expression types,
|
||||
// or an error occurred during evaluation
|
||||
}
|
||||
```
|
||||
|
||||
Error handling
|
||||
--------------
|
||||
|
||||
The constant evaluator provides two methods, `evaluateDirectly()` and `evaluateSilently()`, which
|
||||
differ in error behavior. `evaluateDirectly()` will evaluate the expression as PHP would, including
|
||||
any generated warnings or Errors. `evaluateSilently()` will instead convert warnings and Errors into
|
||||
a `ConstExprEvaluationException`. For example:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use PhpParser\{ConstExprEvaluator, ConstExprEvaluationException};
|
||||
use PhpParser\Node\{Expr, Scalar};
|
||||
|
||||
$evaluator = new ConstExprEvaluator();
|
||||
|
||||
// 10 / 0
|
||||
$expr = new Expr\BinaryOp\Div(new Scalar\LNumber(10), new Scalar\LNumber(0));
|
||||
|
||||
var_dump($evaluator->evaluateDirectly($expr)); // float(INF)
|
||||
// Warning: Division by zero
|
||||
|
||||
try {
|
||||
$evaluator->evaluateSilently($expr);
|
||||
} catch (ConstExprEvaluationException $e) {
|
||||
var_dump($e->getPrevious()->getMessage()); // Division by zero
|
||||
}
|
||||
```
|
||||
|
||||
For the purposes of static analysis, you will likely want to use `evaluateSilently()` and leave
|
||||
erroring expressions unevaluated.
|
||||
|
||||
Unsupported expressions and evaluator fallback
|
||||
----------------------------------------------
|
||||
|
||||
The constant expression evaluator supports all expression types that are permitted in constant
|
||||
expressions, apart from the following:
|
||||
|
||||
* `Scalar\MagicConst\*`
|
||||
* `Expr\ConstFetch` (only null/false/true are handled)
|
||||
* `Expr\ClassConstFetch`
|
||||
|
||||
Handling these expression types requires non-local information, such as which global constants are
|
||||
defined. By default, the evaluator will throw a `ConstExprEvaluationException` when it encounters
|
||||
an unsupported expression type.
|
||||
|
||||
It is possible to override this behavior and support resolution for these expression types by
|
||||
specifying an evaluation fallback function:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use PhpParser\{ConstExprEvaluator, ConstExprEvaluationException};
|
||||
use PhpParser\Node\Expr;
|
||||
|
||||
$evalutator = new ConstExprEvaluator(function(Expr $expr) {
|
||||
if ($expr instanceof Expr\ConstFetch) {
|
||||
return fetchConstantSomehow($expr);
|
||||
}
|
||||
if ($expr instanceof Expr\ClassConstFetch) {
|
||||
return fetchClassConstantSomehow($expr);
|
||||
}
|
||||
// etc.
|
||||
throw new ConstExprEvaluationException(
|
||||
"Expression of type {$expr->getType()} cannot be evaluated");
|
||||
});
|
||||
|
||||
try {
|
||||
$evalutator->evaluateSilently($someExpr);
|
||||
} catch (ConstExprEvaluationException $e) {
|
||||
// Handle exception
|
||||
}
|
||||
```
|
||||
|
||||
Implementers are advised to ensure that evaluation of indirect constant references cannot lead to
|
||||
infinite recursion. For example, the following code could lead to infinite recursion if constant
|
||||
lookup is implemented naively.
|
||||
|
||||
```php
|
||||
<?php
|
||||
class Test {
|
||||
const A = self::B;
|
||||
const B = self::A;
|
||||
}
|
||||
```
|
@ -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
|
||||
the source code of the parsed file. An example for printing an error:
|
||||
|
||||
|
68
doc/component/FAQ.markdown
Normal file
68
doc/component/FAQ.markdown
Normal file
@ -0,0 +1,68 @@
|
||||
Frequently Asked Questions
|
||||
==========================
|
||||
|
||||
* [How can the parent of a node be obtained?](#how-can-the-parent-of-a-node-be-obtained)
|
||||
* [How can the next/previous sibling of a node be obtained?](#how-can-the-nextprevious-sibling-of-a-node-be-obtained)
|
||||
|
||||
How can the parent of a node be obtained?
|
||||
-----
|
||||
|
||||
The AST does not store parent nodes by default. However, it is easy to add a custom parent node
|
||||
attribute using a custom node visitor:
|
||||
|
||||
```php
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
|
||||
class ParentConnector extends NodeVisitorAbstract {
|
||||
private $stack;
|
||||
public function beforeTraverse(array $nodes) {
|
||||
$this->stack = [];
|
||||
}
|
||||
public function enterNode(Node $node) {
|
||||
if (!empty($this->stack)) {
|
||||
$node->setAttribute('parent', $this->stack[count($this->stack)-1]);
|
||||
}
|
||||
$this->stack[] = $node;
|
||||
}
|
||||
public function leaveNode(Node $node) {
|
||||
array_pop($this->stack);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After running this visitor, the parent node can be obtained through `$node->getAttribute('parent')`.
|
||||
|
||||
How can the next/previous sibling of a node be obtained?
|
||||
-----
|
||||
|
||||
Again, siblings are not stored by default, but the visitor from the previous entry can be easily
|
||||
extended to store the previous / next node with a common parent as well:
|
||||
|
||||
```php
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
|
||||
class NodeConnector extends NodeVisitorAbstract {
|
||||
private $stack;
|
||||
private $prev;
|
||||
public function beforeTraverse(array $nodes) {
|
||||
$this->stack = [];
|
||||
$this->prev = null;
|
||||
}
|
||||
public function enterNode(Node $node) {
|
||||
if (!empty($this->stack)) {
|
||||
$node->setAttribute('parent', $this->stack[count($this->stack)-1]);
|
||||
}
|
||||
if ($this->prev && $this->prev->getAttribute('parent') == $node->getAttribute('parent')) {
|
||||
$node->setAttribute('prev', $this->prev);
|
||||
$this->prev->setAttribute('next', $node);
|
||||
}
|
||||
$this->stack[] = $node;
|
||||
}
|
||||
public function leaveNode(Node $node) {
|
||||
$this->prev = $node;
|
||||
array_pop($this->stack);
|
||||
}
|
||||
}
|
||||
```
|
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`.
|
@ -27,18 +27,23 @@ The attributes used in this example match the default behavior of the lexer. The
|
||||
|
||||
* `comments`: Array of `PhpParser\Comment` or `PhpParser\Comment\Doc` instances, representing all comments that occurred
|
||||
between the previous non-discarded token and the current one. Use of this attribute is required for the
|
||||
`$node->getDocComment()` method to work. The attribute is also needed if you wish the pretty printer to retain
|
||||
comments present in the original code.
|
||||
`$node->getComments()` and `$node->getDocComment()` methods to work. The attribute is also needed if you wish the pretty
|
||||
printer to retain comments present in the original code.
|
||||
* `startLine`: Line in which the node starts. This attribute is required for the `$node->getLine()` to work. It is also
|
||||
required if syntax errors should contain line number information.
|
||||
* `endLine`: Line in which the node ends.
|
||||
* `startTokenPos`: Offset into the token array of the first token in the node.
|
||||
* `endTokenPos`: Offset into the token array of the last token in the node.
|
||||
* `startFilePos`: Offset into the code string of the first character that is part of the node.
|
||||
* `endFilePos`: Offset into the code string of the last character that is part of the node.
|
||||
* `endLine`: Line in which the node ends. Required for `$node->getEndLine()`.
|
||||
* `startTokenPos`: Offset into the token array of the first token in the node. Required for `$node->getStartTokenPos()`.
|
||||
* `endTokenPos`: Offset into the token array of the last token in the node. Required for `$node->getEndTokenPos()`.
|
||||
* `startFilePos`: Offset into the code string of the first character that is part of the node. Required for `$node->getStartFilePos()`.
|
||||
* `endFilePos`: Offset into the code string of the last character that is part of the node. Required for `$node->getEndFilePos()`.
|
||||
|
||||
### Using token positions
|
||||
|
||||
> **Note:** The example in this section is outdated in that this information is directly available in the AST: While
|
||||
> `$property->isPublic()` does not distinguish between `public` and `var`, directly checking `$property->flags` for
|
||||
> the `$property->flags & Class_::VISIBILITY_MODIFIER_MASK) === 0` allows making this distinction without resorting to
|
||||
> tokens. However the general idea behind the example still applies in other cases.
|
||||
|
||||
The token offset information is useful if you wish to examine the exact formatting used for a node. For example the AST
|
||||
does not distinguish whether a property was declared using `public` or using `var`, but you can retrieve this
|
||||
information based on the token position:
|
||||
@ -72,7 +77,7 @@ $lexer = new PhpParser\Lexer(array(
|
||||
'comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos'
|
||||
)
|
||||
));
|
||||
$parser = (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::PREFER_PHP7, $lexer);
|
||||
$parser = (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::ONLY_PHP7, $lexer);
|
||||
|
||||
$visitor = new MyNodeVisitor();
|
||||
$traverser = new PhpParser\NodeTraverser();
|
||||
@ -95,14 +100,16 @@ Lexer extension
|
||||
|
||||
A lexer has to define the following public interface:
|
||||
|
||||
void startLexing(string $code, ErrorHandler $errorHandler = null);
|
||||
array getTokens();
|
||||
string handleHaltCompiler();
|
||||
int getNextToken(string &$value = null, array &$startAttributes = null, array &$endAttributes = null);
|
||||
```php
|
||||
function startLexing(string $code, ErrorHandler $errorHandler = null): void;
|
||||
function getTokens(): array;
|
||||
function handleHaltCompiler(): string;
|
||||
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
|
||||
`parse()` method of the parser is called. It can be used to reset state or preprocess the source code or tokens. The
|
||||
passes `ErrorHandler` should be used to report lexing errors.
|
||||
The `startLexing()` method is invoked whenever the `parse()` method of the parser is called and is passed the source
|
||||
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
|
||||
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
|
||||
used by the parser (which uses `getNextToken()`), but is useful in combination with the token position attributes.
|
||||
|
87
doc/component/Name_resolution.markdown
Normal file
87
doc/component/Name_resolution.markdown
Normal file
@ -0,0 +1,87 @@
|
||||
Name resolution
|
||||
===============
|
||||
|
||||
Since the introduction of namespaces in PHP 5.3, literal names in PHP code are subject to a
|
||||
relatively complex name resolution process, which is based on the current namespace, the current
|
||||
import table state, as well the type of the referenced symbol. PHP-Parser implements name
|
||||
resolution and related functionality, both as reusable logic (NameContext), as well as a node
|
||||
visitor (NameResolver) based on it.
|
||||
|
||||
The NameResolver visitor
|
||||
------------------------
|
||||
|
||||
The `NameResolver` visitor can (and for nearly all uses of the AST, is) be applied to resolve names
|
||||
to their fully-qualified form, to the degree that this is possible.
|
||||
|
||||
```php
|
||||
$nameResolver = new PhpParser\NodeVisitor\NameResolver;
|
||||
$nodeTraverser = new PhpParser\NodeTraverser;
|
||||
$nodeTraverser->addVisitor($nameResolver);
|
||||
|
||||
// Resolve names
|
||||
$stmts = $nodeTraverser->traverse($stmts);
|
||||
```
|
||||
|
||||
In the default configuration, the name resolver will perform three actions:
|
||||
|
||||
* Declarations of functions, classes, interfaces, traits and global constants will have a
|
||||
`namespacedName` property added, which contains the function/class/etc name including the
|
||||
namespace prefix. For historic reasons this is a **property** rather than an attribute.
|
||||
* Names will be replaced by fully qualified resolved names, which are instances of
|
||||
`Node\Name\FullyQualified`.
|
||||
* Unqualified function and constant names inside a namespace cannot be statically resolved. Inside
|
||||
a namespace `Foo`, a call to `strlen()` may either refer to the namespaced `\Foo\strlen()`, or
|
||||
the global `\strlen()`. Because PHP-Parser does not have the necessary context to decide this,
|
||||
such names are left unresolved. Additionally a `namespacedName` **attribute** is added to the
|
||||
name node.
|
||||
|
||||
The name resolver accepts an option array as the second argument, with the following default values:
|
||||
|
||||
```php
|
||||
$nameResolver = new PhpParser\NodeVisitor\NameResolver(null, [
|
||||
'preserveOriginalNames' => false,
|
||||
'replaceNodes' => true,
|
||||
]);
|
||||
```
|
||||
|
||||
If the `preserveOriginalNames` option is enabled, then the resolved (fully qualified) name will have
|
||||
an `originalName` attribute, which contains the unresolved name.
|
||||
|
||||
If the `replaceNodes` option is disabled, then names will no longer be resolved in-place. Instead a
|
||||
`resolvedName` attribute will be added to each name, which contains the resolved (fully qualified)
|
||||
name. Once again, if an unqualified function or constant name cannot be resolved, then the
|
||||
`resolvedName` attribute will not be present, and instead a `namespacedName` attribute is added.
|
||||
|
||||
The `replaceNodes` attribute is useful if you wish to perform modifications on the AST, as you
|
||||
probably do not wish the resoluting code to have fully resolved names as a side-effect.
|
||||
|
||||
The NameContext
|
||||
---------------
|
||||
|
||||
The actual name resolution logic is implemented in the `NameContext` class, which has the following
|
||||
public API:
|
||||
|
||||
```php
|
||||
class NameContext {
|
||||
public function __construct(ErrorHandler $errorHandler);
|
||||
public function startNamespace(Name $namespace = null);
|
||||
public function addAlias(Name $name, string $aliasName, int $type, array $errorAttrs = []);
|
||||
|
||||
public function getNamespace();
|
||||
public function getResolvedName(Name $name, int $type);
|
||||
public function getResolvedClassName(Name $name) : Name;
|
||||
public function getPossibleNames(string $name, int $type) : array;
|
||||
public function getShortName(string $name, int $type) : Name;
|
||||
}
|
||||
```
|
||||
|
||||
The `$type` parameters accept on of the `Stmt\Use_::TYPE_*` constants, which represent the three
|
||||
basic symbol types in PHP (functions, constants and everything else).
|
||||
|
||||
Next to name resolution, the `NameContext` also supports the reverse operation of finding a short
|
||||
representation of a name given the current name resolution environment.
|
||||
|
||||
The name context is intended to be used for name resolution operations outside the AST itself, such
|
||||
as class names inside doc comments. A visitor running in parallel with the name resolver can access
|
||||
the name context using `$nameResolver->getNameContext()`. Alternatively a visitor can use an
|
||||
independent context and explicitly feed `Namespace` and `Use` nodes to it.
|
65
doc/component/Performance.markdown
Normal file
65
doc/component/Performance.markdown
Normal file
@ -0,0 +1,65 @@
|
||||
Performance
|
||||
===========
|
||||
|
||||
Parsing is computationally expensive task, to which the PHP language is not very well suited.
|
||||
Nonetheless, there are a few things you can do to improve the performance of this library, which are
|
||||
described in the following.
|
||||
|
||||
Xdebug
|
||||
------
|
||||
|
||||
Running PHP with XDebug adds a lot of overhead, especially for code that performs many method calls.
|
||||
Just by loading XDebug (without enabling profiling or other more intrusive XDebug features), you
|
||||
can expect that code using PHP-Parser will be approximately *five times slower*.
|
||||
|
||||
As such, you should make sure that XDebug is not loaded when using this library. Note that setting
|
||||
the `xdebug.default_enable=0` ini option does *not* disable XDebug. The *only* way to disable
|
||||
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),
|
||||
you may want to consider automatically restarting PHP with XDebug unloaded. The
|
||||
[composer/xdebug-handler](https://github.com/composer/xdebug-handler) package can be used to do
|
||||
this.
|
||||
|
||||
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
|
||||
the AST uses recursion and will generate an error if the value of this option is too low.
|
||||
|
||||
Assertions
|
||||
----------
|
||||
|
||||
Assertions should be disabled in a production context by setting `zend.assertions=-1` (or
|
||||
`zend.assertions=0` if set at runtime). The library currently doesn't make heavy use of assertions,
|
||||
but they are used in an increasing number of places.
|
||||
|
||||
Object reuse
|
||||
------------
|
||||
|
||||
Many objects in this project are designed for reuse. For example, one `Parser` object can be used to
|
||||
parse multiple files.
|
||||
|
||||
When possible, objects should be reused rather than being newly instantiated for every use. Some
|
||||
objects have expensive initialization procedures, which will be unnecessarily repeated if the object
|
||||
is not reused. (Currently two objects with particularly expensive setup are lexers and pretty
|
||||
printers, though the details might change between versions of this library.)
|
||||
|
||||
Garbage collection
|
||||
------------------
|
||||
|
||||
A limitation in PHP's cyclic garbage collector may lead to major performance degradation when the
|
||||
active working set exceeds 10000 objects (or arrays). Especially when parsing very large files this
|
||||
limit is significantly exceeded and PHP will spend the majority of time performing unnecessary
|
||||
garbage collection attempts.
|
||||
|
||||
Without GC, parsing time is roughly linear in the input size. With GC, this degenerates to quadratic
|
||||
runtime for large files. While the specifics may differ, as a rough guideline you may expect a 2.5x
|
||||
GC overhead for 500KB files and a 5x overhead for 1MB files.
|
||||
|
||||
Because this a limitation in PHP's implementation, there is no easy way to work around this. If
|
||||
possible, you should avoid parsing very large files, as they will impact overall execution time
|
||||
disproportionally (and are usually generated anyway).
|
||||
|
||||
Of course, you can also try to (temporarily) disable GC. By design the AST generated by PHP-Parser
|
||||
is cycle-free, so the AST itself will never cause leaks with GC disabled. However, other code
|
||||
(including for example the parser object itself) may hold cycles, so disabling of GC should be
|
||||
approached with care.
|
96
doc/component/Pretty_printing.markdown
Normal file
96
doc/component/Pretty_printing.markdown
Normal file
@ -0,0 +1,96 @@
|
||||
Pretty printing
|
||||
===============
|
||||
|
||||
Pretty printing is the process of converting a syntax tree back to PHP code. In its basic mode of
|
||||
operation the pretty printer provided by this library will print the AST using a certain predefined
|
||||
code style and will discard (nearly) all formatting of the original code. Because programmers tend
|
||||
to be rather picky about their code formatting, this mode of operation is not very suitable for
|
||||
refactoring code, but can be used for automatically generated code, which is usually only read for
|
||||
debugging purposes.
|
||||
|
||||
Basic usage
|
||||
-----------
|
||||
|
||||
```php
|
||||
$stmts = $parser->parse($code);
|
||||
|
||||
// MODIFY $stmts here
|
||||
|
||||
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
|
||||
$newCode = $prettyPrinter->prettyPrintFile($stmts);
|
||||
```
|
||||
|
||||
The pretty printer has three basic printing methods: `prettyPrint()`, `prettyPrintFile()` and
|
||||
`prettyPrintExpr()`. The one that is most commonly useful is `prettyPrintFile()`, which takes an
|
||||
array of statements and produces a full PHP file, including opening `<?php`.
|
||||
|
||||
`prettyPrint()` also takes a statement array, but produces code which is valid inside an already
|
||||
open `<?php` context. Lastly, `prettyPrintExpr()` takes an `Expr` node and prints only a single
|
||||
expression.
|
||||
|
||||
Customizing the formatting
|
||||
--------------------------
|
||||
|
||||
Apart from an `shortArraySyntax` option, the default pretty printer does not provide any
|
||||
functionality to customize the formatting of the generated code. The pretty printer does respect a
|
||||
number of `kind` attributes used by some notes (e.g., whether an integer should be printed as
|
||||
decimal, hexadecimal, etc), but there are no options to control brace placement or similar.
|
||||
|
||||
If you want to make minor changes to the formatting, the easiest way is to extend the pretty printer
|
||||
and override the methods responsible for the node types you are interested in.
|
||||
|
||||
If you want to have more fine-grained formatting control, the recommended method is to combine the
|
||||
default pretty printer with an existing library for code reformatting, such as
|
||||
[PHP-CS-Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer).
|
||||
|
||||
Formatting-preserving pretty printing
|
||||
-------------------------------------
|
||||
|
||||
> **Note:** This functionality is **experimental** and not yet complete.
|
||||
|
||||
For automated code refactoring, migration and similar, you will usually only want to modify a small
|
||||
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.
|
||||
|
||||
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 that have not changed) and only reformat
|
||||
code which has been modified or newly inserted.
|
||||
|
||||
Use of the formatting-preservation functionality requires some additional preparatory steps:
|
||||
|
||||
```php
|
||||
use PhpParser\{Lexer, NodeTraverser, NodeVisitor, Parser, PrettyPrinter};
|
||||
|
||||
$lexer = new Lexer\Emulative([
|
||||
'usedAttributes' => [
|
||||
'comments',
|
||||
'startLine', 'endLine',
|
||||
'startTokenPos', 'endTokenPos',
|
||||
],
|
||||
]);
|
||||
$parser = new Parser\Php7($lexer);
|
||||
|
||||
$traverser = new NodeTraverser();
|
||||
$traverser->addVisitor(new NodeVisitor\CloningVisitor());
|
||||
|
||||
$printer = new PrettyPrinter\Standard();
|
||||
|
||||
$oldStmts = $parser->parse($code);
|
||||
$oldTokens = $lexer->getTokens();
|
||||
|
||||
$newStmts = $traverser->traverse($oldStmts);
|
||||
|
||||
// MODIFY $newStmts HERE
|
||||
|
||||
$newCode = $printer->printFormatPreserving($newStmts, $oldStmts, $oldTokens);
|
||||
```
|
||||
|
||||
If you make use of the name resolution functionality, you will likely want to disable the
|
||||
`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
|
||||
[name resolution documentation](Name_resolution.markdown).
|
||||
|
||||
This functionality is experimental and not yet fully implemented. It should not provide incorrect
|
||||
code, but it may sometimes reformat more code than necessary. Open issues are tracked in
|
||||
[issue #344](https://github.com/nikic/PHP-Parser/issues/344). If you encounter problems while using
|
||||
this functionality, please open an issue, so we know what to prioritize.
|
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.
|
@ -11,7 +11,7 @@ What do all those files mean?
|
||||
.phpy pseudo language
|
||||
=====================
|
||||
|
||||
The `.y` file is a normal grammer in `kmyacc` (`yacc`) style, with some transformations
|
||||
The `.y` file is a normal grammar in `kmyacc` (`yacc`) style, with some transformations
|
||||
applied to it:
|
||||
|
||||
* Nodes are created using the syntax `Name[..., ...]`. This is transformed into
|
||||
|
@ -2,8 +2,8 @@
|
||||
$meta #
|
||||
#semval($) $this->semValue
|
||||
#semval($,%t) $this->semValue
|
||||
#semval(%n) $this->stackPos-(%l-%n)
|
||||
#semval(%n,%t) $this->stackPos-(%l-%n)
|
||||
#semval(%n) $stackPos-(%l-%n)
|
||||
#semval(%n,%t) $stackPos-(%l-%n)
|
||||
|
||||
namespace PhpParser\Parser;
|
||||
|
||||
@ -32,8 +32,8 @@ class #(-p) extends \PhpParser\ParserAbstract
|
||||
protected $defaultAction = #(YYDEFAULT);
|
||||
protected $unexpectedTokenRule = #(YYUNEXPECTED);
|
||||
|
||||
protected $YY2TBLSTATE = #(YY2TBLSTATE);
|
||||
protected $YYNLSTATES = #(YYNLSTATES);
|
||||
protected $YY2TBLSTATE = #(YY2TBLSTATE);
|
||||
protected $numNonLeafStates = #(YYNLSTATES);
|
||||
|
||||
protected $symbolToName = array(
|
||||
#listvar terminals
|
||||
@ -88,16 +88,19 @@ class #(-p) extends \PhpParser\ParserAbstract
|
||||
#production-strings;
|
||||
);
|
||||
#endif
|
||||
|
||||
protected function initReduceCallbacks() {
|
||||
$this->reduceCallbacks = [
|
||||
#reduce
|
||||
|
||||
protected function reduceRule%n() {
|
||||
%b
|
||||
}
|
||||
%n => function ($stackPos) {
|
||||
%b
|
||||
},
|
||||
#noact
|
||||
|
||||
protected function reduceRule%n() {
|
||||
$this->semValue = $this->semStack[$this->stackPos];
|
||||
}
|
||||
%n => function ($stackPos) {
|
||||
$this->semValue = $this->semStack[$stackPos];
|
||||
},
|
||||
#endreduce
|
||||
];
|
||||
}
|
||||
}
|
||||
#tailcode;
|
||||
|
171
grammar/php5.y
171
grammar/php5.y
@ -16,7 +16,7 @@ top_statement_list_ex:
|
||||
|
||||
top_statement_list:
|
||||
top_statement_list_ex
|
||||
{ makeNop($nop, $this->lookaheadStartAttributes);
|
||||
{ makeNop($nop, $this->lookaheadStartAttributes, $this->endAttributes);
|
||||
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
||||
;
|
||||
|
||||
@ -35,9 +35,17 @@ semi_reserved:
|
||||
| T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC
|
||||
;
|
||||
|
||||
identifier_ex:
|
||||
T_STRING { $$ = Node\Identifier[$1]; }
|
||||
| semi_reserved { $$ = Node\Identifier[$1]; }
|
||||
;
|
||||
|
||||
identifier:
|
||||
T_STRING { $$ = $1; }
|
||||
| semi_reserved { $$ = $1; }
|
||||
T_STRING { $$ = Node\Identifier[$1]; }
|
||||
;
|
||||
|
||||
reserved_non_modifiers_identifier:
|
||||
reserved_non_modifiers { $$ = Node\Identifier[$1]; }
|
||||
;
|
||||
|
||||
namespace_name_parts:
|
||||
@ -49,6 +57,10 @@ namespace_name:
|
||||
namespace_name_parts { $$ = Name[$1]; }
|
||||
;
|
||||
|
||||
plain_variable:
|
||||
T_VARIABLE { $$ = Expr\Variable[parseVar($1)]; }
|
||||
;
|
||||
|
||||
top_statement:
|
||||
statement { $$ = $1; }
|
||||
| function_declaration_statement { $$ = $1; }
|
||||
@ -56,11 +68,17 @@ top_statement:
|
||||
| T_HALT_COMPILER
|
||||
{ $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; }
|
||||
| T_NAMESPACE namespace_name ';'
|
||||
{ $$ = Stmt\Namespace_[$2, null]; $this->checkNamespace($$); }
|
||||
{ $$ = Stmt\Namespace_[$2, null];
|
||||
$$->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON);
|
||||
$this->checkNamespace($$); }
|
||||
| T_NAMESPACE namespace_name '{' top_statement_list '}'
|
||||
{ $$ = Stmt\Namespace_[$2, $4]; $this->checkNamespace($$); }
|
||||
{ $$ = Stmt\Namespace_[$2, $4];
|
||||
$$->setAttribute('kind', Stmt\Namespace_::KIND_BRACED);
|
||||
$this->checkNamespace($$); }
|
||||
| T_NAMESPACE '{' top_statement_list '}'
|
||||
{ $$ = Stmt\Namespace_[null, $3]; $this->checkNamespace($$); }
|
||||
{ $$ = Stmt\Namespace_[null, $3];
|
||||
$$->setAttribute('kind', Stmt\Namespace_::KIND_BRACED);
|
||||
$this->checkNamespace($$); }
|
||||
| T_USE use_declarations ';' { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; }
|
||||
| T_USE use_type use_declarations ';' { $$ = Stmt\Use_[$3, $2]; }
|
||||
| group_use_declaration ';' { $$ = $1; }
|
||||
@ -103,7 +121,7 @@ inline_use_declarations:
|
||||
unprefixed_use_declaration:
|
||||
namespace_name
|
||||
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
|
||||
| namespace_name T_AS T_STRING
|
||||
| namespace_name T_AS identifier
|
||||
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
|
||||
;
|
||||
|
||||
@ -123,7 +141,7 @@ constant_declaration_list:
|
||||
;
|
||||
|
||||
constant_declaration:
|
||||
T_STRING '=' static_scalar { $$ = Node\Const_[$1, $3]; }
|
||||
identifier '=' static_scalar { $$ = Node\Const_[$1, $3]; }
|
||||
;
|
||||
|
||||
class_const_list:
|
||||
@ -132,7 +150,7 @@ class_const_list:
|
||||
;
|
||||
|
||||
class_const:
|
||||
identifier '=' static_scalar { $$ = Node\Const_[$1, $3]; }
|
||||
identifier_ex '=' static_scalar { $$ = Node\Const_[$1, $3]; }
|
||||
;
|
||||
|
||||
inner_statement_list_ex:
|
||||
@ -142,7 +160,7 @@ inner_statement_list_ex:
|
||||
|
||||
inner_statement_list:
|
||||
inner_statement_list_ex
|
||||
{ makeNop($nop, $this->lookaheadStartAttributes);
|
||||
{ makeNop($nop, $this->lookaheadStartAttributes, $this->endAttributes);
|
||||
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
||||
;
|
||||
|
||||
@ -155,7 +173,15 @@ inner_statement:
|
||||
;
|
||||
|
||||
non_empty_statement:
|
||||
'{' inner_statement_list '}' { $$ = $2; prependLeadingComments($$); }
|
||||
'{' inner_statement_list '}'
|
||||
{
|
||||
if ($2) {
|
||||
$$ = $2; prependLeadingComments($$);
|
||||
} else {
|
||||
makeNop($$, $this->startAttributeStack[#1], $this->endAttributes);
|
||||
if (null === $$) { $$ = array(); }
|
||||
}
|
||||
}
|
||||
| T_IF parentheses_expr statement elseif_list else_single
|
||||
{ $$ = Stmt\If_[$2, ['stmts' => toArray($3), 'elseifs' => $4, 'else' => $5]]; }
|
||||
| T_IF parentheses_expr ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';'
|
||||
@ -171,12 +197,12 @@ non_empty_statement:
|
||||
| T_CONTINUE expr ';' { $$ = Stmt\Continue_[$2]; }
|
||||
| T_RETURN ';' { $$ = Stmt\Return_[null]; }
|
||||
| T_RETURN expr ';' { $$ = Stmt\Return_[$2]; }
|
||||
| yield_expr ';' { $$ = $1; }
|
||||
| T_GLOBAL global_var_list ';' { $$ = Stmt\Global_[$2]; }
|
||||
| T_STATIC static_var_list ';' { $$ = Stmt\Static_[$2]; }
|
||||
| T_ECHO expr_list ';' { $$ = Stmt\Echo_[$2]; }
|
||||
| T_INLINE_HTML { $$ = Stmt\InlineHTML[$1]; }
|
||||
| expr ';' { $$ = $1; }
|
||||
| yield_expr ';' { $$ = Stmt\Expression[$1]; }
|
||||
| expr ';' { $$ = Stmt\Expression[$1]; }
|
||||
| T_UNSET '(' variables_list ')' ';' { $$ = Stmt\Unset_[$3]; }
|
||||
| T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement
|
||||
{ $$ = Stmt\Foreach_[$3, $5[0], ['keyVar' => null, 'byRef' => $5[1], 'stmts' => $7]]; }
|
||||
@ -186,16 +212,16 @@ non_empty_statement:
|
||||
| T_TRY '{' inner_statement_list '}' catches optional_finally
|
||||
{ $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); }
|
||||
| T_THROW expr ';' { $$ = Stmt\Throw_[$2]; }
|
||||
| T_GOTO T_STRING ';' { $$ = Stmt\Goto_[$2]; }
|
||||
| T_STRING ':' { $$ = Stmt\Label[$1]; }
|
||||
| expr error { $$ = $1; }
|
||||
| T_GOTO identifier ';' { $$ = Stmt\Goto_[$2]; }
|
||||
| identifier ':' { $$ = Stmt\Label[$1]; }
|
||||
| expr error { $$ = Stmt\Expression[$1]; }
|
||||
| error { $$ = array(); /* means: no statement */ }
|
||||
;
|
||||
|
||||
statement:
|
||||
non_empty_statement { $$ = $1; }
|
||||
| ';'
|
||||
{ makeNop($$, $this->startAttributeStack[#1]);
|
||||
{ makeNop($$, $this->startAttributeStack[#1], $this->endAttributes);
|
||||
if ($$ === null) $$ = array(); /* means: no statement */ }
|
||||
;
|
||||
|
||||
@ -205,8 +231,8 @@ catches:
|
||||
;
|
||||
|
||||
catch:
|
||||
T_CATCH '(' name T_VARIABLE ')' '{' inner_statement_list '}'
|
||||
{ $$ = Stmt\Catch_[array($3), parseVar($4), $7]; }
|
||||
T_CATCH '(' name plain_variable ')' '{' inner_statement_list '}'
|
||||
{ $$ = Stmt\Catch_[array($3), $4, $7]; }
|
||||
;
|
||||
|
||||
optional_finally:
|
||||
@ -230,18 +256,18 @@ optional_ellipsis:
|
||||
;
|
||||
|
||||
function_declaration_statement:
|
||||
T_FUNCTION optional_ref T_STRING '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
|
||||
T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
|
||||
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $9]]; }
|
||||
;
|
||||
|
||||
class_declaration_statement:
|
||||
class_entry_type T_STRING extends_from implements_list '{' class_statement_list '}'
|
||||
class_entry_type identifier extends_from implements_list '{' class_statement_list '}'
|
||||
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6]];
|
||||
$this->checkClass($$, #2); }
|
||||
| T_INTERFACE T_STRING interface_extends_list '{' class_statement_list '}'
|
||||
| T_INTERFACE identifier interface_extends_list '{' class_statement_list '}'
|
||||
{ $$ = Stmt\Interface_[$2, ['extends' => $3, 'stmts' => $5]];
|
||||
$this->checkInterface($$, #2); }
|
||||
| T_TRAIT T_STRING '{' class_statement_list '}'
|
||||
| T_TRAIT identifier '{' class_statement_list '}'
|
||||
{ $$ = Stmt\Trait_[$2, ['stmts' => $4]]; }
|
||||
;
|
||||
|
||||
@ -293,7 +319,7 @@ declare_list:
|
||||
;
|
||||
|
||||
declare_list_element:
|
||||
T_STRING '=' static_scalar { $$ = Stmt\DeclareDeclare[$1, $3]; }
|
||||
identifier '=' static_scalar { $$ = Stmt\DeclareDeclare[$1, $3]; }
|
||||
;
|
||||
|
||||
switch_case_list:
|
||||
@ -309,8 +335,8 @@ case_list:
|
||||
;
|
||||
|
||||
case:
|
||||
T_CASE expr case_separator inner_statement_list { $$ = Stmt\Case_[$2, $4]; }
|
||||
| T_DEFAULT case_separator inner_statement_list { $$ = Stmt\Case_[null, $3]; }
|
||||
T_CASE expr case_separator inner_statement_list_ex { $$ = Stmt\Case_[$2, $4]; }
|
||||
| T_DEFAULT case_separator inner_statement_list_ex { $$ = Stmt\Case_[null, $3]; }
|
||||
;
|
||||
|
||||
case_separator:
|
||||
@ -368,16 +394,16 @@ non_empty_parameter_list:
|
||||
;
|
||||
|
||||
parameter:
|
||||
optional_param_type optional_ref optional_ellipsis T_VARIABLE
|
||||
{ $$ = Node\Param[parseVar($4), null, $1, $2, $3]; $this->checkParam($$); }
|
||||
| optional_param_type optional_ref optional_ellipsis T_VARIABLE '=' static_scalar
|
||||
{ $$ = Node\Param[parseVar($4), $6, $1, $2, $3]; $this->checkParam($$); }
|
||||
optional_param_type optional_ref optional_ellipsis plain_variable
|
||||
{ $$ = Node\Param[$4, null, $1, $2, $3]; $this->checkParam($$); }
|
||||
| optional_param_type optional_ref optional_ellipsis plain_variable '=' static_scalar
|
||||
{ $$ = Node\Param[$4, $6, $1, $2, $3]; $this->checkParam($$); }
|
||||
;
|
||||
|
||||
type:
|
||||
name { $$ = $1; }
|
||||
| T_ARRAY { $$ = 'array'; }
|
||||
| T_CALLABLE { $$ = 'callable'; }
|
||||
| T_ARRAY { $$ = Node\Identifier['array']; }
|
||||
| T_CALLABLE { $$ = Node\Identifier['callable']; }
|
||||
;
|
||||
|
||||
optional_param_type:
|
||||
@ -413,7 +439,7 @@ global_var_list:
|
||||
;
|
||||
|
||||
global_var:
|
||||
T_VARIABLE { $$ = Expr\Variable[parseVar($1)]; }
|
||||
plain_variable { $$ = $1; }
|
||||
| '$' variable { $$ = Expr\Variable[$2]; }
|
||||
| '$' '{' expr '}' { $$ = Expr\Variable[$3]; }
|
||||
;
|
||||
@ -424,8 +450,8 @@ static_var_list:
|
||||
;
|
||||
|
||||
static_var:
|
||||
T_VARIABLE { $$ = Stmt\StaticVar[parseVar($1), null]; }
|
||||
| T_VARIABLE '=' static_scalar { $$ = Stmt\StaticVar[parseVar($1), $3]; }
|
||||
plain_variable { $$ = Stmt\StaticVar[$1, null]; }
|
||||
| plain_variable '=' static_scalar { $$ = Stmt\StaticVar[$1, $3]; }
|
||||
;
|
||||
|
||||
class_statement_list:
|
||||
@ -437,7 +463,7 @@ class_statement:
|
||||
variable_modifiers property_declaration_list ';'
|
||||
{ $$ = Stmt\Property[$1, $2]; $this->checkProperty($$, #1); }
|
||||
| T_CONST class_const_list ';' { $$ = Stmt\ClassConst[$2, 0]; }
|
||||
| method_modifiers T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type method_body
|
||||
| method_modifiers T_FUNCTION optional_ref identifier_ex '(' parameter_list ')' optional_return_type method_body
|
||||
{ $$ = Stmt\ClassMethod[$4, ['type' => $1, 'byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9]];
|
||||
$this->checkClassMethod($$, #1); }
|
||||
| T_USE class_name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; }
|
||||
@ -456,22 +482,22 @@ trait_adaptation_list:
|
||||
trait_adaptation:
|
||||
trait_method_reference_fully_qualified T_INSTEADOF class_name_list ';'
|
||||
{ $$ = Stmt\TraitUseAdaptation\Precedence[$1[0], $1[1], $3]; }
|
||||
| trait_method_reference T_AS member_modifier identifier ';'
|
||||
| trait_method_reference T_AS member_modifier identifier_ex ';'
|
||||
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, $4]; }
|
||||
| trait_method_reference T_AS member_modifier ';'
|
||||
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, null]; }
|
||||
| trait_method_reference T_AS T_STRING ';'
|
||||
| trait_method_reference T_AS identifier ';'
|
||||
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
|
||||
| trait_method_reference T_AS reserved_non_modifiers ';'
|
||||
| trait_method_reference T_AS reserved_non_modifiers_identifier ';'
|
||||
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
|
||||
;
|
||||
|
||||
trait_method_reference_fully_qualified:
|
||||
name T_PAAMAYIM_NEKUDOTAYIM identifier { $$ = array($1, $3); }
|
||||
name T_PAAMAYIM_NEKUDOTAYIM identifier_ex { $$ = array($1, $3); }
|
||||
;
|
||||
trait_method_reference:
|
||||
trait_method_reference_fully_qualified { $$ = $1; }
|
||||
| identifier { $$ = array(null, $1); }
|
||||
| identifier_ex { $$ = array(null, $1); }
|
||||
;
|
||||
|
||||
method_body:
|
||||
@ -508,9 +534,13 @@ property_declaration_list:
|
||||
| property_declaration_list ',' property_declaration { push($1, $3); }
|
||||
;
|
||||
|
||||
property_decl_name:
|
||||
T_VARIABLE { $$ = Node\VarLikeIdentifier[parseVar($1)]; }
|
||||
;
|
||||
|
||||
property_declaration:
|
||||
T_VARIABLE { $$ = Stmt\PropertyProperty[parseVar($1), null]; }
|
||||
| T_VARIABLE '=' static_scalar { $$ = Stmt\PropertyProperty[parseVar($1), $3]; }
|
||||
property_decl_name { $$ = Stmt\PropertyProperty[$1, null]; }
|
||||
| property_decl_name '=' static_scalar { $$ = Stmt\PropertyProperty[$1, $3]; }
|
||||
;
|
||||
|
||||
expr_list:
|
||||
@ -651,6 +681,7 @@ anonymous_class:
|
||||
T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}'
|
||||
{ $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $3, 'implements' => $4, 'stmts' => $6]], $2);
|
||||
$this->checkClass($$[0], -1); }
|
||||
;
|
||||
|
||||
new_expr:
|
||||
T_NEW class_name_reference ctor_arguments { $$ = Expr\New_[$2, $3]; }
|
||||
@ -669,30 +700,17 @@ lexical_var_list:
|
||||
;
|
||||
|
||||
lexical_var:
|
||||
optional_ref T_VARIABLE { $$ = Expr\ClosureUse[parseVar($2), $1]; }
|
||||
optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; }
|
||||
;
|
||||
|
||||
function_call:
|
||||
name argument_list { $$ = Expr\FuncCall[$1, $2]; }
|
||||
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier argument_list
|
||||
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex argument_list
|
||||
{ $$ = Expr\StaticCall[$1, $3, $4]; }
|
||||
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '{' expr '}' argument_list
|
||||
{ $$ = Expr\StaticCall[$1, $4, $6]; }
|
||||
| static_property argument_list {
|
||||
if ($1 instanceof Node\Expr\StaticPropertyFetch) {
|
||||
$$ = Expr\StaticCall[$1->class, Expr\Variable[$1->name], $2];
|
||||
} elseif ($1 instanceof Node\Expr\ArrayDimFetch) {
|
||||
$tmp = $1;
|
||||
while ($tmp->var instanceof Node\Expr\ArrayDimFetch) {
|
||||
$tmp = $tmp->var;
|
||||
}
|
||||
|
||||
$$ = Expr\StaticCall[$tmp->var->class, $1, $2];
|
||||
$tmp->var = Expr\Variable[$tmp->var->name];
|
||||
} else {
|
||||
throw new \Exception;
|
||||
}
|
||||
}
|
||||
| static_property argument_list
|
||||
{ $$ = $this->fixupPhp5StaticPropCall($1, $2, attributes()); }
|
||||
| variable_without_objects argument_list
|
||||
{ $$ = Expr\FuncCall[$1, $2]; }
|
||||
| function_call '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
@ -776,7 +794,7 @@ common_scalar:
|
||||
|
||||
static_scalar:
|
||||
common_scalar { $$ = $1; }
|
||||
| class_name T_PAAMAYIM_NEKUDOTAYIM identifier { $$ = Expr\ClassConstFetch[$1, $3]; }
|
||||
| class_name T_PAAMAYIM_NEKUDOTAYIM identifier_ex { $$ = Expr\ClassConstFetch[$1, $3]; }
|
||||
| name { $$ = Expr\ConstFetch[$1]; }
|
||||
| T_ARRAY '(' static_array_pair_list ')' { $$ = Expr\Array_[$3]; }
|
||||
| '[' static_array_pair_list ']' { $$ = Expr\Array_[$2]; }
|
||||
@ -821,7 +839,7 @@ static_operation:
|
||||
|
||||
constant:
|
||||
name { $$ = Expr\ConstFetch[$1]; }
|
||||
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier
|
||||
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex
|
||||
{ $$ = Expr\ClassConstFetch[$1, $3]; }
|
||||
;
|
||||
|
||||
@ -900,9 +918,14 @@ static_property:
|
||||
| static_property_with_arrays { $$ = $1; }
|
||||
;
|
||||
|
||||
static_property_simple_name:
|
||||
T_VARIABLE
|
||||
{ $var = parseVar($1); $$ = \is_string($var) ? Node\VarLikeIdentifier[$var] : $var; }
|
||||
;
|
||||
|
||||
static_property_with_arrays:
|
||||
class_name_or_var T_PAAMAYIM_NEKUDOTAYIM T_VARIABLE
|
||||
{ $$ = Expr\StaticPropertyFetch[$1, parseVar($3)]; }
|
||||
class_name_or_var T_PAAMAYIM_NEKUDOTAYIM static_property_simple_name
|
||||
{ $$ = Expr\StaticPropertyFetch[$1, $3]; }
|
||||
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '$' '{' expr '}'
|
||||
{ $$ = Expr\StaticPropertyFetch[$1, $5]; }
|
||||
| static_property_with_arrays '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
@ -912,7 +935,7 @@ static_property_with_arrays:
|
||||
reference_variable:
|
||||
reference_variable '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| reference_variable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| T_VARIABLE { $$ = Expr\Variable[parseVar($1)]; }
|
||||
| plain_variable { $$ = $1; }
|
||||
| '$' '{' expr '}' { $$ = Expr\Variable[$3]; }
|
||||
;
|
||||
|
||||
@ -922,7 +945,7 @@ dim_offset:
|
||||
;
|
||||
|
||||
object_property:
|
||||
T_STRING { $$ = $1; }
|
||||
identifier { $$ = $1; }
|
||||
| '{' expr '}' { $$ = $2; }
|
||||
| variable_without_objects { $$ = $1; }
|
||||
| error { $$ = Expr\Error[]; $this->errorState = 2; }
|
||||
@ -971,25 +994,25 @@ encaps_string_part:
|
||||
T_ENCAPSED_AND_WHITESPACE { $$ = Scalar\EncapsedStringPart[$1]; }
|
||||
;
|
||||
|
||||
encaps_base_var:
|
||||
T_VARIABLE { $$ = Expr\Variable[parseVar($1)]; }
|
||||
encaps_str_varname:
|
||||
T_STRING_VARNAME { $$ = Expr\Variable[$1]; }
|
||||
;
|
||||
|
||||
encaps_var:
|
||||
encaps_base_var { $$ = $1; }
|
||||
| encaps_base_var '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| encaps_base_var T_OBJECT_OPERATOR T_STRING { $$ = Expr\PropertyFetch[$1, $3]; }
|
||||
plain_variable { $$ = $1; }
|
||||
| plain_variable '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| plain_variable T_OBJECT_OPERATOR identifier { $$ = Expr\PropertyFetch[$1, $3]; }
|
||||
| T_DOLLAR_OPEN_CURLY_BRACES expr '}' { $$ = Expr\Variable[$2]; }
|
||||
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}' { $$ = Expr\Variable[$2]; }
|
||||
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '[' expr ']' '}'
|
||||
{ $$ = Expr\ArrayDimFetch[Expr\Variable[$2], $4]; }
|
||||
| T_DOLLAR_OPEN_CURLY_BRACES encaps_str_varname '[' expr ']' '}'
|
||||
{ $$ = Expr\ArrayDimFetch[$2, $4]; }
|
||||
| T_CURLY_OPEN variable '}' { $$ = $2; }
|
||||
;
|
||||
|
||||
encaps_var_offset:
|
||||
T_STRING { $$ = Scalar\String_[$1]; }
|
||||
| T_NUM_STRING { $$ = $this->parseNumString($1, attributes()); }
|
||||
| T_VARIABLE { $$ = Expr\Variable[parseVar($1)]; }
|
||||
| plain_variable { $$ = $1; }
|
||||
;
|
||||
|
||||
%%
|
||||
|
164
grammar/php7.y
164
grammar/php7.y
@ -16,7 +16,7 @@ top_statement_list_ex:
|
||||
|
||||
top_statement_list:
|
||||
top_statement_list_ex
|
||||
{ makeNop($nop, $this->lookaheadStartAttributes);
|
||||
{ makeNop($nop, $this->lookaheadStartAttributes, $this->endAttributes);
|
||||
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
||||
;
|
||||
|
||||
@ -35,9 +35,17 @@ semi_reserved:
|
||||
| T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC
|
||||
;
|
||||
|
||||
identifier_ex:
|
||||
T_STRING { $$ = Node\Identifier[$1]; }
|
||||
| semi_reserved { $$ = Node\Identifier[$1]; }
|
||||
;
|
||||
|
||||
identifier:
|
||||
T_STRING { $$ = $1; }
|
||||
| semi_reserved { $$ = $1; }
|
||||
T_STRING { $$ = Node\Identifier[$1]; }
|
||||
;
|
||||
|
||||
reserved_non_modifiers_identifier:
|
||||
reserved_non_modifiers { $$ = Node\Identifier[$1]; }
|
||||
;
|
||||
|
||||
namespace_name_parts:
|
||||
@ -49,6 +57,10 @@ namespace_name:
|
||||
namespace_name_parts { $$ = Name[$1]; }
|
||||
;
|
||||
|
||||
plain_variable:
|
||||
T_VARIABLE { $$ = Expr\Variable[parseVar($1)]; }
|
||||
;
|
||||
|
||||
semi:
|
||||
';' { /* nothing */ }
|
||||
| error { /* nothing */ }
|
||||
@ -59,6 +71,10 @@ no_comma:
|
||||
| ',' { $this->emitError(new Error('A trailing comma is not allowed here', attributes())); }
|
||||
;
|
||||
|
||||
optional_comma:
|
||||
/* empty */
|
||||
| ','
|
||||
|
||||
top_statement:
|
||||
statement { $$ = $1; }
|
||||
| function_declaration_statement { $$ = $1; }
|
||||
@ -66,11 +82,17 @@ top_statement:
|
||||
| T_HALT_COMPILER
|
||||
{ $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; }
|
||||
| T_NAMESPACE namespace_name semi
|
||||
{ $$ = Stmt\Namespace_[$2, null]; $this->checkNamespace($$); }
|
||||
{ $$ = Stmt\Namespace_[$2, null];
|
||||
$$->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON);
|
||||
$this->checkNamespace($$); }
|
||||
| T_NAMESPACE namespace_name '{' top_statement_list '}'
|
||||
{ $$ = Stmt\Namespace_[$2, $4]; $this->checkNamespace($$); }
|
||||
{ $$ = Stmt\Namespace_[$2, $4];
|
||||
$$->setAttribute('kind', Stmt\Namespace_::KIND_BRACED);
|
||||
$this->checkNamespace($$); }
|
||||
| T_NAMESPACE '{' top_statement_list '}'
|
||||
{ $$ = Stmt\Namespace_[null, $3]; $this->checkNamespace($$); }
|
||||
{ $$ = Stmt\Namespace_[null, $3];
|
||||
$$->setAttribute('kind', Stmt\Namespace_::KIND_BRACED);
|
||||
$this->checkNamespace($$); }
|
||||
| T_USE use_declarations semi { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; }
|
||||
| T_USE use_type use_declarations semi { $$ = Stmt\Use_[$3, $2]; }
|
||||
| group_use_declaration semi { $$ = $1; }
|
||||
@ -95,7 +117,7 @@ group_use_declaration:
|
||||
;
|
||||
|
||||
unprefixed_use_declarations:
|
||||
non_empty_unprefixed_use_declarations no_comma { $$ = $1; }
|
||||
non_empty_unprefixed_use_declarations optional_comma { $$ = $1; }
|
||||
;
|
||||
|
||||
non_empty_unprefixed_use_declarations:
|
||||
@ -114,7 +136,7 @@ non_empty_use_declarations:
|
||||
;
|
||||
|
||||
inline_use_declarations:
|
||||
non_empty_inline_use_declarations no_comma { $$ = $1; }
|
||||
non_empty_inline_use_declarations optional_comma { $$ = $1; }
|
||||
;
|
||||
|
||||
non_empty_inline_use_declarations:
|
||||
@ -126,7 +148,7 @@ non_empty_inline_use_declarations:
|
||||
unprefixed_use_declaration:
|
||||
namespace_name
|
||||
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
|
||||
| namespace_name T_AS T_STRING
|
||||
| namespace_name T_AS identifier
|
||||
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
|
||||
;
|
||||
|
||||
@ -151,7 +173,7 @@ non_empty_constant_declaration_list:
|
||||
;
|
||||
|
||||
constant_declaration:
|
||||
T_STRING '=' expr { $$ = Node\Const_[$1, $3]; }
|
||||
identifier '=' expr { $$ = Node\Const_[$1, $3]; }
|
||||
;
|
||||
|
||||
class_const_list:
|
||||
@ -164,7 +186,7 @@ non_empty_class_const_list:
|
||||
;
|
||||
|
||||
class_const:
|
||||
identifier '=' expr { $$ = Node\Const_[$1, $3]; }
|
||||
identifier_ex '=' expr { $$ = Node\Const_[$1, $3]; }
|
||||
;
|
||||
|
||||
inner_statement_list_ex:
|
||||
@ -174,7 +196,7 @@ inner_statement_list_ex:
|
||||
|
||||
inner_statement_list:
|
||||
inner_statement_list_ex
|
||||
{ makeNop($nop, $this->lookaheadStartAttributes);
|
||||
{ makeNop($nop, $this->lookaheadStartAttributes, $this->endAttributes);
|
||||
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
||||
;
|
||||
|
||||
@ -187,7 +209,15 @@ inner_statement:
|
||||
;
|
||||
|
||||
non_empty_statement:
|
||||
'{' inner_statement_list '}' { $$ = $2; prependLeadingComments($$); }
|
||||
'{' inner_statement_list '}'
|
||||
{
|
||||
if ($2) {
|
||||
$$ = $2; prependLeadingComments($$);
|
||||
} else {
|
||||
makeNop($$, $this->startAttributeStack[#1], $this->endAttributes);
|
||||
if (null === $$) { $$ = array(); }
|
||||
}
|
||||
}
|
||||
| T_IF '(' expr ')' statement elseif_list else_single
|
||||
{ $$ = Stmt\If_[$3, ['stmts' => toArray($5), 'elseifs' => $6, 'else' => $7]]; }
|
||||
| T_IF '(' expr ')' ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';'
|
||||
@ -204,7 +234,7 @@ non_empty_statement:
|
||||
| T_STATIC static_var_list semi { $$ = Stmt\Static_[$2]; }
|
||||
| T_ECHO expr_list semi { $$ = Stmt\Echo_[$2]; }
|
||||
| T_INLINE_HTML { $$ = Stmt\InlineHTML[$1]; }
|
||||
| expr semi { $$ = $1; }
|
||||
| expr semi { $$ = Stmt\Expression[$1]; }
|
||||
| T_UNSET '(' variables_list ')' semi { $$ = Stmt\Unset_[$3]; }
|
||||
| T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement
|
||||
{ $$ = Stmt\Foreach_[$3, $5[0], ['keyVar' => null, 'byRef' => $5[1], 'stmts' => $7]]; }
|
||||
@ -214,15 +244,15 @@ non_empty_statement:
|
||||
| T_TRY '{' inner_statement_list '}' catches optional_finally
|
||||
{ $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); }
|
||||
| T_THROW expr semi { $$ = Stmt\Throw_[$2]; }
|
||||
| T_GOTO T_STRING semi { $$ = Stmt\Goto_[$2]; }
|
||||
| T_STRING ':' { $$ = Stmt\Label[$1]; }
|
||||
| T_GOTO identifier semi { $$ = Stmt\Goto_[$2]; }
|
||||
| identifier ':' { $$ = Stmt\Label[$1]; }
|
||||
| error { $$ = array(); /* means: no statement */ }
|
||||
;
|
||||
|
||||
statement:
|
||||
non_empty_statement { $$ = $1; }
|
||||
| ';'
|
||||
{ makeNop($$, $this->startAttributeStack[#1]);
|
||||
{ makeNop($$, $this->startAttributeStack[#1], $this->endAttributes);
|
||||
if ($$ === null) $$ = array(); /* means: no statement */ }
|
||||
;
|
||||
|
||||
@ -237,8 +267,8 @@ name_union:
|
||||
;
|
||||
|
||||
catch:
|
||||
T_CATCH '(' name_union T_VARIABLE ')' '{' inner_statement_list '}'
|
||||
{ $$ = Stmt\Catch_[$3, parseVar($4), $7]; }
|
||||
T_CATCH '(' name_union plain_variable ')' '{' inner_statement_list '}'
|
||||
{ $$ = Stmt\Catch_[$3, $4, $7]; }
|
||||
;
|
||||
|
||||
optional_finally:
|
||||
@ -247,7 +277,7 @@ optional_finally:
|
||||
;
|
||||
|
||||
variables_list:
|
||||
non_empty_variables_list no_comma { $$ = $1; }
|
||||
non_empty_variables_list optional_comma { $$ = $1; }
|
||||
;
|
||||
|
||||
non_empty_variables_list:
|
||||
@ -266,18 +296,18 @@ optional_ellipsis:
|
||||
;
|
||||
|
||||
function_declaration_statement:
|
||||
T_FUNCTION optional_ref T_STRING '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
|
||||
T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
|
||||
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $9]]; }
|
||||
;
|
||||
|
||||
class_declaration_statement:
|
||||
class_entry_type T_STRING extends_from implements_list '{' class_statement_list '}'
|
||||
class_entry_type identifier extends_from implements_list '{' class_statement_list '}'
|
||||
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6]];
|
||||
$this->checkClass($$, #2); }
|
||||
| T_INTERFACE T_STRING interface_extends_list '{' class_statement_list '}'
|
||||
| T_INTERFACE identifier interface_extends_list '{' class_statement_list '}'
|
||||
{ $$ = Stmt\Interface_[$2, ['extends' => $3, 'stmts' => $5]];
|
||||
$this->checkInterface($$, #2); }
|
||||
| T_TRAIT T_STRING '{' class_statement_list '}'
|
||||
| T_TRAIT identifier '{' class_statement_list '}'
|
||||
{ $$ = Stmt\Trait_[$2, ['stmts' => $4]]; }
|
||||
;
|
||||
|
||||
@ -337,7 +367,7 @@ non_empty_declare_list:
|
||||
;
|
||||
|
||||
declare_list_element:
|
||||
T_STRING '=' expr { $$ = Stmt\DeclareDeclare[$1, $3]; }
|
||||
identifier '=' expr { $$ = Stmt\DeclareDeclare[$1, $3]; }
|
||||
;
|
||||
|
||||
switch_case_list:
|
||||
@ -353,8 +383,8 @@ case_list:
|
||||
;
|
||||
|
||||
case:
|
||||
T_CASE expr case_separator inner_statement_list { $$ = Stmt\Case_[$2, $4]; }
|
||||
| T_DEFAULT case_separator inner_statement_list { $$ = Stmt\Case_[null, $3]; }
|
||||
T_CASE expr case_separator inner_statement_list_ex { $$ = Stmt\Case_[$2, $4]; }
|
||||
| T_DEFAULT case_separator inner_statement_list_ex { $$ = Stmt\Case_[null, $3]; }
|
||||
;
|
||||
|
||||
case_separator:
|
||||
@ -413,10 +443,10 @@ non_empty_parameter_list:
|
||||
;
|
||||
|
||||
parameter:
|
||||
optional_param_type optional_ref optional_ellipsis T_VARIABLE
|
||||
{ $$ = Node\Param[parseVar($4), null, $1, $2, $3]; $this->checkParam($$); }
|
||||
| optional_param_type optional_ref optional_ellipsis T_VARIABLE '=' expr
|
||||
{ $$ = Node\Param[parseVar($4), $6, $1, $2, $3]; $this->checkParam($$); }
|
||||
optional_param_type optional_ref optional_ellipsis plain_variable
|
||||
{ $$ = Node\Param[$4, null, $1, $2, $3]; $this->checkParam($$); }
|
||||
| optional_param_type optional_ref optional_ellipsis plain_variable '=' expr
|
||||
{ $$ = Node\Param[$4, $6, $1, $2, $3]; $this->checkParam($$); }
|
||||
;
|
||||
|
||||
type_expr:
|
||||
@ -426,8 +456,8 @@ type_expr:
|
||||
|
||||
type:
|
||||
name { $$ = $this->handleBuiltinTypes($1); }
|
||||
| T_ARRAY { $$ = 'array'; }
|
||||
| T_CALLABLE { $$ = 'callable'; }
|
||||
| T_ARRAY { $$ = Node\Identifier['array']; }
|
||||
| T_CALLABLE { $$ = Node\Identifier['callable']; }
|
||||
;
|
||||
|
||||
optional_param_type:
|
||||
@ -442,7 +472,7 @@ optional_return_type:
|
||||
|
||||
argument_list:
|
||||
'(' ')' { $$ = array(); }
|
||||
| '(' non_empty_argument_list no_comma ')' { $$ = $2; }
|
||||
| '(' non_empty_argument_list optional_comma ')' { $$ = $2; }
|
||||
;
|
||||
|
||||
non_empty_argument_list:
|
||||
@ -479,8 +509,8 @@ non_empty_static_var_list:
|
||||
;
|
||||
|
||||
static_var:
|
||||
T_VARIABLE { $$ = Stmt\StaticVar[parseVar($1), null]; }
|
||||
| T_VARIABLE '=' expr { $$ = Stmt\StaticVar[parseVar($1), $3]; }
|
||||
plain_variable { $$ = Stmt\StaticVar[$1, null]; }
|
||||
| plain_variable '=' expr { $$ = Stmt\StaticVar[$1, $3]; }
|
||||
;
|
||||
|
||||
class_statement_list:
|
||||
@ -493,7 +523,7 @@ class_statement:
|
||||
{ $$ = Stmt\Property[$1, $2]; $this->checkProperty($$, #1); }
|
||||
| method_modifiers T_CONST class_const_list ';'
|
||||
{ $$ = Stmt\ClassConst[$3, $1]; $this->checkClassConst($$, #1); }
|
||||
| method_modifiers T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type method_body
|
||||
| method_modifiers T_FUNCTION optional_ref identifier_ex '(' parameter_list ')' optional_return_type method_body
|
||||
{ $$ = Stmt\ClassMethod[$4, ['type' => $1, 'byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9]];
|
||||
$this->checkClassMethod($$, #1); }
|
||||
| T_USE class_name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; }
|
||||
@ -512,22 +542,22 @@ trait_adaptation_list:
|
||||
trait_adaptation:
|
||||
trait_method_reference_fully_qualified T_INSTEADOF class_name_list ';'
|
||||
{ $$ = Stmt\TraitUseAdaptation\Precedence[$1[0], $1[1], $3]; }
|
||||
| trait_method_reference T_AS member_modifier identifier ';'
|
||||
| trait_method_reference T_AS member_modifier identifier_ex ';'
|
||||
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, $4]; }
|
||||
| trait_method_reference T_AS member_modifier ';'
|
||||
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, null]; }
|
||||
| trait_method_reference T_AS T_STRING ';'
|
||||
| trait_method_reference T_AS identifier ';'
|
||||
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
|
||||
| trait_method_reference T_AS reserved_non_modifiers ';'
|
||||
| trait_method_reference T_AS reserved_non_modifiers_identifier ';'
|
||||
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
|
||||
;
|
||||
|
||||
trait_method_reference_fully_qualified:
|
||||
name T_PAAMAYIM_NEKUDOTAYIM identifier { $$ = array($1, $3); }
|
||||
name T_PAAMAYIM_NEKUDOTAYIM identifier_ex { $$ = array($1, $3); }
|
||||
;
|
||||
trait_method_reference:
|
||||
trait_method_reference_fully_qualified { $$ = $1; }
|
||||
| identifier { $$ = array(null, $1); }
|
||||
| identifier_ex { $$ = array(null, $1); }
|
||||
;
|
||||
|
||||
method_body:
|
||||
@ -569,9 +599,13 @@ non_empty_property_declaration_list:
|
||||
{ push($1, $3); }
|
||||
;
|
||||
|
||||
property_decl_name:
|
||||
T_VARIABLE { $$ = Node\VarLikeIdentifier[parseVar($1)]; }
|
||||
;
|
||||
|
||||
property_declaration:
|
||||
T_VARIABLE { $$ = Stmt\PropertyProperty[parseVar($1), null]; }
|
||||
| T_VARIABLE '=' expr { $$ = Stmt\PropertyProperty[parseVar($1), $3]; }
|
||||
property_decl_name { $$ = Stmt\PropertyProperty[$1, null]; }
|
||||
| property_decl_name '=' expr { $$ = Stmt\PropertyProperty[$1, $3]; }
|
||||
;
|
||||
|
||||
expr_list:
|
||||
@ -685,6 +719,7 @@ anonymous_class:
|
||||
T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}'
|
||||
{ $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $3, 'implements' => $4, 'stmts' => $6]], $2);
|
||||
$this->checkClass($$[0], -1); }
|
||||
;
|
||||
|
||||
new_expr:
|
||||
T_NEW class_name_reference ctor_arguments { $$ = Expr\New_[$2, $3]; }
|
||||
@ -707,7 +742,7 @@ non_empty_lexical_var_list:
|
||||
;
|
||||
|
||||
lexical_var:
|
||||
optional_ref T_VARIABLE { $$ = Expr\ClosureUse[parseVar($2), $1]; }
|
||||
optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; }
|
||||
;
|
||||
|
||||
function_call:
|
||||
@ -758,7 +793,7 @@ ctor_arguments:
|
||||
|
||||
constant:
|
||||
name { $$ = Expr\ConstFetch[$1]; }
|
||||
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier
|
||||
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex
|
||||
{ $$ = Expr\ClassConstFetch[$1, $3]; }
|
||||
/* We interpret and isolated FOO:: as an unfinished class constant fetch. It could also be
|
||||
an unfinished static property fetch or unfinished scoped call. */
|
||||
@ -849,8 +884,13 @@ simple_variable:
|
||||
| '$' error { $$ = Expr\Error[]; $this->errorState = 2; }
|
||||
;
|
||||
|
||||
static_member_prop_name:
|
||||
simple_variable
|
||||
{ $var = $1; $$ = \is_string($var) ? Node\VarLikeIdentifier[$var] : $var; }
|
||||
;
|
||||
|
||||
static_member:
|
||||
class_name_or_var T_PAAMAYIM_NEKUDOTAYIM simple_variable
|
||||
class_name_or_var T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name
|
||||
{ $$ = Expr\StaticPropertyFetch[$1, $3]; }
|
||||
;
|
||||
|
||||
@ -859,18 +899,20 @@ new_variable:
|
||||
| new_variable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| new_variable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| new_variable T_OBJECT_OPERATOR property_name { $$ = Expr\PropertyFetch[$1, $3]; }
|
||||
| class_name T_PAAMAYIM_NEKUDOTAYIM simple_variable { $$ = Expr\StaticPropertyFetch[$1, $3]; }
|
||||
| new_variable T_PAAMAYIM_NEKUDOTAYIM simple_variable { $$ = Expr\StaticPropertyFetch[$1, $3]; }
|
||||
| class_name T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name
|
||||
{ $$ = Expr\StaticPropertyFetch[$1, $3]; }
|
||||
| new_variable T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name
|
||||
{ $$ = Expr\StaticPropertyFetch[$1, $3]; }
|
||||
;
|
||||
|
||||
member_name:
|
||||
identifier { $$ = $1; }
|
||||
identifier_ex { $$ = $1; }
|
||||
| '{' expr '}' { $$ = $2; }
|
||||
| simple_variable { $$ = Expr\Variable[$1]; }
|
||||
;
|
||||
|
||||
property_name:
|
||||
T_STRING { $$ = $1; }
|
||||
identifier { $$ = $1; }
|
||||
| '{' expr '}' { $$ = $2; }
|
||||
| simple_variable { $$ = Expr\Variable[$1]; }
|
||||
| error { $$ = Expr\Error[]; $this->errorState = 2; }
|
||||
@ -887,15 +929,17 @@ list_expr_elements:
|
||||
|
||||
list_expr_element:
|
||||
variable { $$ = Expr\ArrayItem[$1, null, false]; }
|
||||
| '&' variable { $$ = Expr\ArrayItem[$2, null, true]; }
|
||||
| list_expr { $$ = Expr\ArrayItem[$1, null, 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]; }
|
||||
| /* empty */ { $$ = null; }
|
||||
;
|
||||
|
||||
array_pair_list:
|
||||
inner_array_pair_list
|
||||
{ $$ = $1; $end = count($$)-1; if ($$[$end] === null) unset($$[$end]); }
|
||||
{ $$ = $1; $end = count($$)-1; if ($$[$end] === null) array_pop($$); }
|
||||
;
|
||||
|
||||
inner_array_pair_list:
|
||||
@ -922,18 +966,18 @@ encaps_string_part:
|
||||
T_ENCAPSED_AND_WHITESPACE { $$ = Scalar\EncapsedStringPart[$1]; }
|
||||
;
|
||||
|
||||
encaps_base_var:
|
||||
T_VARIABLE { $$ = Expr\Variable[parseVar($1)]; }
|
||||
encaps_str_varname:
|
||||
T_STRING_VARNAME { $$ = Expr\Variable[$1]; }
|
||||
;
|
||||
|
||||
encaps_var:
|
||||
encaps_base_var { $$ = $1; }
|
||||
| encaps_base_var '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| encaps_base_var T_OBJECT_OPERATOR T_STRING { $$ = Expr\PropertyFetch[$1, $3]; }
|
||||
plain_variable { $$ = $1; }
|
||||
| plain_variable '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| plain_variable T_OBJECT_OPERATOR identifier { $$ = Expr\PropertyFetch[$1, $3]; }
|
||||
| T_DOLLAR_OPEN_CURLY_BRACES expr '}' { $$ = Expr\Variable[$2]; }
|
||||
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}' { $$ = Expr\Variable[$2]; }
|
||||
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '[' expr ']' '}'
|
||||
{ $$ = Expr\ArrayDimFetch[Expr\Variable[$2], $4]; }
|
||||
| T_DOLLAR_OPEN_CURLY_BRACES encaps_str_varname '[' expr ']' '}'
|
||||
{ $$ = Expr\ArrayDimFetch[$2, $4]; }
|
||||
| T_CURLY_OPEN variable '}' { $$ = $2; }
|
||||
;
|
||||
|
||||
@ -941,7 +985,7 @@ encaps_var_offset:
|
||||
T_STRING { $$ = Scalar\String_[$1]; }
|
||||
| T_NUM_STRING { $$ = $this->parseNumString($1, attributes()); }
|
||||
| '-' T_NUM_STRING { $$ = $this->parseNumString('-' . $2, attributes()); }
|
||||
| T_VARIABLE { $$ = Expr\Variable[parseVar($1)]; }
|
||||
| plain_variable { $$ = $1; }
|
||||
;
|
||||
|
||||
%%
|
||||
|
@ -176,11 +176,11 @@ function resolveMacros($code) {
|
||||
}
|
||||
|
||||
if ('makeNop' == $name) {
|
||||
assertArgs(2, $args, $name);
|
||||
assertArgs(3, $args, $name);
|
||||
|
||||
return '$startAttributes = ' . $args[1] . ';'
|
||||
. ' if (isset($startAttributes[\'comments\']))'
|
||||
. ' { ' . $args[0] . ' = new Stmt\Nop([\'comments\' => $startAttributes[\'comments\']]); }'
|
||||
. ' { ' . $args[0] . ' = new Stmt\Nop($startAttributes + ' . $args[2] . '); }'
|
||||
. ' else { ' . $args[0] . ' = null; }';
|
||||
}
|
||||
|
||||
@ -205,7 +205,7 @@ function resolveMacros($code) {
|
||||
assertArgs(1, $args, $name);
|
||||
|
||||
return '$attrs = $this->startAttributeStack[#1]; $stmts = ' . $args[0] . '; '
|
||||
. 'if (!empty($attrs[\'comments\']) && isset($stmts[0])) {'
|
||||
. 'if (!empty($attrs[\'comments\'])) {'
|
||||
. '$stmts[0]->setAttribute(\'comments\', '
|
||||
. 'array_merge($attrs[\'comments\'], $stmts[0]->getAttribute(\'comments\', []))); }';
|
||||
}
|
||||
|
@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class Autoloader
|
||||
{
|
||||
/** @var bool Whether the autoloader has been registered. */
|
||||
private static $registered = false;
|
||||
|
||||
/**
|
||||
* Registers PhpParser\Autoloader as an SPL autoloader.
|
||||
*
|
||||
* @param bool $prepend Whether to prepend the autoloader instead of appending
|
||||
*/
|
||||
static public function register($prepend = false) {
|
||||
if (self::$registered === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
spl_autoload_register(array(__CLASS__, 'autoload'), true, $prepend);
|
||||
self::$registered = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles autoloading of classes.
|
||||
*
|
||||
* @param string $class A class name.
|
||||
*/
|
||||
static public function autoload($class) {
|
||||
if (0 === strpos($class, 'PhpParser\\')) {
|
||||
$fileName = __DIR__ . strtr(substr($class, 9), '\\', '/') . '.php';
|
||||
if (file_exists($fileName)) {
|
||||
require $fileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
@ -9,5 +9,5 @@ interface Builder
|
||||
*
|
||||
* @return Node The built node
|
||||
*/
|
||||
public function getNode();
|
||||
}
|
||||
public function getNode() : Node;
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
@ -11,20 +12,20 @@ class Class_ extends Declaration
|
||||
protected $name;
|
||||
|
||||
protected $extends = null;
|
||||
protected $implements = array();
|
||||
protected $implements = [];
|
||||
protected $flags = 0;
|
||||
|
||||
protected $uses = array();
|
||||
protected $constants = array();
|
||||
protected $properties = array();
|
||||
protected $methods = array();
|
||||
protected $uses = [];
|
||||
protected $constants = [];
|
||||
protected $properties = [];
|
||||
protected $methods = [];
|
||||
|
||||
/**
|
||||
* Creates a class builder.
|
||||
*
|
||||
* @param string $name Name of the class
|
||||
*/
|
||||
public function __construct($name) {
|
||||
public function __construct(string $name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
@ -36,7 +37,7 @@ class Class_ extends Declaration
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function extend($class) {
|
||||
$this->extends = $this->normalizeName($class);
|
||||
$this->extends = BuilderHelpers::normalizeName($class);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -48,9 +49,9 @@ class Class_ extends Declaration
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function implement() {
|
||||
foreach (func_get_args() as $interface) {
|
||||
$this->implements[] = $this->normalizeName($interface);
|
||||
public function implement(...$interfaces) {
|
||||
foreach ($interfaces as $interface) {
|
||||
$this->implements[] = BuilderHelpers::normalizeName($interface);
|
||||
}
|
||||
|
||||
return $this;
|
||||
@ -62,7 +63,7 @@ class Class_ extends Declaration
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeAbstract() {
|
||||
$this->setModifier(Stmt\Class_::MODIFIER_ABSTRACT);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -73,7 +74,7 @@ class Class_ extends Declaration
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeFinal() {
|
||||
$this->setModifier(Stmt\Class_::MODIFIER_FINAL);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -86,21 +87,21 @@ class Class_ extends Declaration
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function addStmt($stmt) {
|
||||
$stmt = $this->normalizeNode($stmt);
|
||||
$stmt = BuilderHelpers::normalizeNode($stmt);
|
||||
|
||||
$targets = array(
|
||||
'Stmt_TraitUse' => &$this->uses,
|
||||
'Stmt_ClassConst' => &$this->constants,
|
||||
'Stmt_Property' => &$this->properties,
|
||||
'Stmt_ClassMethod' => &$this->methods,
|
||||
);
|
||||
$targets = [
|
||||
Stmt\TraitUse::class => &$this->uses,
|
||||
Stmt\ClassConst::class => &$this->constants,
|
||||
Stmt\Property::class => &$this->properties,
|
||||
Stmt\ClassMethod::class => &$this->methods,
|
||||
];
|
||||
|
||||
$type = $stmt->getType();
|
||||
if (!isset($targets[$type])) {
|
||||
throw new \LogicException(sprintf('Unexpected node of type "%s"', $type));
|
||||
$class = \get_class($stmt);
|
||||
if (!isset($targets[$class])) {
|
||||
throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType()));
|
||||
}
|
||||
|
||||
$targets[$type][] = $stmt;
|
||||
$targets[$class][] = $stmt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -110,12 +111,12 @@ class Class_ extends Declaration
|
||||
*
|
||||
* @return Stmt\Class_ The built class node
|
||||
*/
|
||||
public function getNode() {
|
||||
return new Stmt\Class_($this->name, array(
|
||||
public function getNode() : PhpParser\Node {
|
||||
return new Stmt\Class_($this->name, [
|
||||
'flags' => $this->flags,
|
||||
'extends' => $this->extends,
|
||||
'implements' => $this->implements,
|
||||
'stmts' => array_merge($this->uses, $this->constants, $this->properties, $this->methods),
|
||||
), $this->attributes);
|
||||
], $this->attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
|
||||
abstract class Declaration extends PhpParser\BuilderAbstract
|
||||
abstract class Declaration implements PhpParser\Builder
|
||||
{
|
||||
protected $attributes = array();
|
||||
protected $attributes = [];
|
||||
|
||||
abstract public function addStmt($stmt);
|
||||
|
||||
@ -33,10 +34,10 @@ abstract class Declaration extends PhpParser\BuilderAbstract
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function setDocComment($docComment) {
|
||||
$this->attributes['comments'] = array(
|
||||
$this->normalizeDocComment($docComment)
|
||||
);
|
||||
$this->attributes['comments'] = [
|
||||
BuilderHelpers::normalizeDocComment($docComment)
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
|
||||
abstract class FunctionLike extends Declaration
|
||||
{
|
||||
protected $returnByRef = false;
|
||||
protected $params = array();
|
||||
protected $params = [];
|
||||
|
||||
/** @var string|Node\Name|Node\NullableType|null */
|
||||
protected $returnType = null;
|
||||
@ -32,7 +32,7 @@ abstract class FunctionLike extends Declaration
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function addParam($param) {
|
||||
$param = $this->normalizeNode($param);
|
||||
$param = BuilderHelpers::normalizeNode($param);
|
||||
|
||||
if (!$param instanceof Node\Param) {
|
||||
throw new \LogicException(sprintf('Expected parameter node, got "%s"', $param->getType()));
|
||||
@ -61,14 +61,13 @@ abstract class FunctionLike extends Declaration
|
||||
/**
|
||||
* Sets the return type for PHP 7.
|
||||
*
|
||||
* @param string|Node\Name|Node\NullableType $type One of array, callable, string, int, float, bool, iterable,
|
||||
* or a class/interface name.
|
||||
* @param string|Node\Name|Node\NullableType $type One of array, callable, string, int, float,
|
||||
* bool, iterable, or a class/interface name.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function setReturnType($type)
|
||||
{
|
||||
$this->returnType = $this->normalizeType($type);
|
||||
public function setReturnType($type) {
|
||||
$this->returnType = BuilderHelpers::normalizeType($type);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -1,22 +1,23 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Function_ extends FunctionLike
|
||||
{
|
||||
protected $name;
|
||||
protected $stmts = array();
|
||||
protected $stmts = [];
|
||||
|
||||
/**
|
||||
* Creates a function builder.
|
||||
*
|
||||
* @param string $name Name of the function
|
||||
*/
|
||||
public function __construct($name) {
|
||||
public function __construct(string $name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
@ -28,7 +29,7 @@ class Function_ extends FunctionLike
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function addStmt($stmt) {
|
||||
$this->stmts[] = $this->normalizeNode($stmt);
|
||||
$this->stmts[] = BuilderHelpers::normalizeStmt($stmt);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -38,12 +39,12 @@ class Function_ extends FunctionLike
|
||||
*
|
||||
* @return Stmt\Function_ The built function node
|
||||
*/
|
||||
public function getNode() {
|
||||
return new Stmt\Function_($this->name, array(
|
||||
public function getNode() : Node {
|
||||
return new Stmt\Function_($this->name, [
|
||||
'byRef' => $this->returnByRef,
|
||||
'params' => $this->params,
|
||||
'returnType' => $this->returnType,
|
||||
'stmts' => $this->stmts,
|
||||
), $this->attributes);
|
||||
], $this->attributes);
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,25 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Interface_ extends Declaration
|
||||
{
|
||||
protected $name;
|
||||
protected $extends = array();
|
||||
protected $constants = array();
|
||||
protected $methods = array();
|
||||
protected $extends = [];
|
||||
protected $constants = [];
|
||||
protected $methods = [];
|
||||
|
||||
/**
|
||||
* Creates an interface builder.
|
||||
*
|
||||
* @param string $name Name of the interface
|
||||
*/
|
||||
public function __construct($name) {
|
||||
public function __construct(string $name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
@ -29,9 +30,9 @@ class Interface_ extends Declaration
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function extend() {
|
||||
foreach (func_get_args() as $interface) {
|
||||
$this->extends[] = $this->normalizeName($interface);
|
||||
public function extend(...$interfaces) {
|
||||
foreach ($interfaces as $interface) {
|
||||
$this->extends[] = BuilderHelpers::normalizeName($interface);
|
||||
}
|
||||
|
||||
return $this;
|
||||
@ -45,22 +46,16 @@ class Interface_ extends Declaration
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function addStmt($stmt) {
|
||||
$stmt = $this->normalizeNode($stmt);
|
||||
$stmt = BuilderHelpers::normalizeNode($stmt);
|
||||
|
||||
$type = $stmt->getType();
|
||||
switch ($type) {
|
||||
case 'Stmt_ClassConst':
|
||||
$this->constants[] = $stmt;
|
||||
break;
|
||||
|
||||
case 'Stmt_ClassMethod':
|
||||
// we erase all statements in the body of an interface method
|
||||
$stmt->stmts = null;
|
||||
$this->methods[] = $stmt;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \LogicException(sprintf('Unexpected node of type "%s"', $type));
|
||||
if ($stmt instanceof Stmt\ClassConst) {
|
||||
$this->constants[] = $stmt;
|
||||
} elseif ($stmt instanceof Stmt\ClassMethod) {
|
||||
// we erase all statements in the body of an interface method
|
||||
$stmt->stmts = null;
|
||||
$this->methods[] = $stmt;
|
||||
} else {
|
||||
throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType()));
|
||||
}
|
||||
|
||||
return $this;
|
||||
@ -71,10 +66,10 @@ class Interface_ extends Declaration
|
||||
*
|
||||
* @return Stmt\Interface_ The built interface node
|
||||
*/
|
||||
public function getNode() {
|
||||
return new Stmt\Interface_($this->name, array(
|
||||
public function getNode() : PhpParser\Node {
|
||||
return new Stmt\Interface_($this->name, [
|
||||
'extends' => $this->extends,
|
||||
'stmts' => array_merge($this->constants, $this->methods),
|
||||
), $this->attributes);
|
||||
], $this->attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
@ -12,14 +13,14 @@ class Method extends FunctionLike
|
||||
protected $flags = 0;
|
||||
|
||||
/** @var array|null */
|
||||
protected $stmts = array();
|
||||
protected $stmts = [];
|
||||
|
||||
/**
|
||||
* Creates a method builder.
|
||||
*
|
||||
* @param string $name Name of the method
|
||||
*/
|
||||
public function __construct($name) {
|
||||
public function __construct(string $name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
@ -29,7 +30,7 @@ class Method extends FunctionLike
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePublic() {
|
||||
$this->setModifier(Stmt\Class_::MODIFIER_PUBLIC);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PUBLIC);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -40,7 +41,7 @@ class Method extends FunctionLike
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeProtected() {
|
||||
$this->setModifier(Stmt\Class_::MODIFIER_PROTECTED);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -51,7 +52,7 @@ class Method extends FunctionLike
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePrivate() {
|
||||
$this->setModifier(Stmt\Class_::MODIFIER_PRIVATE);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -62,7 +63,7 @@ class Method extends FunctionLike
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeStatic() {
|
||||
$this->setModifier(Stmt\Class_::MODIFIER_STATIC);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_STATIC);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -77,7 +78,7 @@ class Method extends FunctionLike
|
||||
throw new \LogicException('Cannot make method with statements abstract');
|
||||
}
|
||||
|
||||
$this->setModifier(Stmt\Class_::MODIFIER_ABSTRACT);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT);
|
||||
$this->stmts = null; // abstract methods don't have statements
|
||||
|
||||
return $this;
|
||||
@ -89,7 +90,7 @@ class Method extends FunctionLike
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeFinal() {
|
||||
$this->setModifier(Stmt\Class_::MODIFIER_FINAL);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -106,7 +107,7 @@ class Method extends FunctionLike
|
||||
throw new \LogicException('Cannot add statements to an abstract method');
|
||||
}
|
||||
|
||||
$this->stmts[] = $this->normalizeNode($stmt);
|
||||
$this->stmts[] = BuilderHelpers::normalizeStmt($stmt);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -116,13 +117,13 @@ class Method extends FunctionLike
|
||||
*
|
||||
* @return Stmt\ClassMethod The built method node
|
||||
*/
|
||||
public function getNode() {
|
||||
return new Stmt\ClassMethod($this->name, array(
|
||||
public function getNode() : Node {
|
||||
return new Stmt\ClassMethod($this->name, [
|
||||
'flags' => $this->flags,
|
||||
'byRef' => $this->returnByRef,
|
||||
'params' => $this->params,
|
||||
'returnType' => $this->returnType,
|
||||
'stmts' => $this->stmts,
|
||||
), $this->attributes);
|
||||
], $this->attributes);
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,16 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Namespace_ extends PhpParser\BuilderAbstract
|
||||
class Namespace_ extends Declaration
|
||||
{
|
||||
private $name;
|
||||
private $stmts = array();
|
||||
private $stmts = [];
|
||||
|
||||
/**
|
||||
* Creates a namespace builder.
|
||||
@ -17,7 +18,7 @@ class Namespace_ extends PhpParser\BuilderAbstract
|
||||
* @param Node\Name|string|null $name Name of the namespace
|
||||
*/
|
||||
public function __construct($name) {
|
||||
$this->name = null !== $name ? $this->normalizeName($name) : null;
|
||||
$this->name = null !== $name ? BuilderHelpers::normalizeName($name) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -28,22 +29,7 @@ class Namespace_ extends PhpParser\BuilderAbstract
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function addStmt($stmt) {
|
||||
$this->stmts[] = $this->normalizeNode($stmt);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds multiple statements.
|
||||
*
|
||||
* @param array $stmts The statements to add
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function addStmts(array $stmts) {
|
||||
foreach ($stmts as $stmt) {
|
||||
$this->addStmt($stmt);
|
||||
}
|
||||
$this->stmts[] = BuilderHelpers::normalizeStmt($stmt);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -53,7 +39,7 @@ class Namespace_ extends PhpParser\BuilderAbstract
|
||||
*
|
||||
* @return Node The built node
|
||||
*/
|
||||
public function getNode() {
|
||||
return new Stmt\Namespace_($this->name, $this->stmts);
|
||||
public function getNode() : Node {
|
||||
return new Stmt\Namespace_($this->name, $this->stmts, $this->attributes);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
|
||||
class Param extends PhpParser\BuilderAbstract
|
||||
class Param implements PhpParser\Builder
|
||||
{
|
||||
protected $name;
|
||||
|
||||
@ -16,12 +17,14 @@ class Param extends PhpParser\BuilderAbstract
|
||||
|
||||
protected $byRef = false;
|
||||
|
||||
protected $variadic = false;
|
||||
|
||||
/**
|
||||
* Creates a parameter builder.
|
||||
*
|
||||
* @param string $name Name of the parameter
|
||||
*/
|
||||
public function __construct($name) {
|
||||
public function __construct(string $name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
@ -33,7 +36,7 @@ class Param extends PhpParser\BuilderAbstract
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function setDefault($value) {
|
||||
$this->default = $this->normalizeValue($value);
|
||||
$this->default = BuilderHelpers::normalizeValue($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -46,8 +49,8 @@ class Param extends PhpParser\BuilderAbstract
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function setTypeHint($type) {
|
||||
$this->type = $this->normalizeType($type);
|
||||
if ($this->type === 'void') {
|
||||
$this->type = BuilderHelpers::normalizeType($type);
|
||||
if ($this->type == 'void') {
|
||||
throw new \LogicException('Parameter type cannot be void');
|
||||
}
|
||||
|
||||
@ -65,14 +68,26 @@ class Param extends PhpParser\BuilderAbstract
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the parameter variadic
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeVariadic() {
|
||||
$this->variadic = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the built parameter node.
|
||||
*
|
||||
* @return Node\Param The built parameter node
|
||||
*/
|
||||
public function getNode() {
|
||||
public function getNode() : Node {
|
||||
return new Node\Param(
|
||||
$this->name, $this->default, $this->type, $this->byRef
|
||||
new Node\Expr\Variable($this->name),
|
||||
$this->default, $this->type, $this->byRef, $this->variadic
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,25 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Property extends PhpParser\BuilderAbstract
|
||||
class Property implements PhpParser\Builder
|
||||
{
|
||||
protected $name;
|
||||
|
||||
protected $flags = 0;
|
||||
protected $default = null;
|
||||
protected $attributes = array();
|
||||
protected $attributes = [];
|
||||
|
||||
/**
|
||||
* Creates a property builder.
|
||||
*
|
||||
* @param string $name Name of the property
|
||||
*/
|
||||
public function __construct($name) {
|
||||
public function __construct(string $name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
@ -28,7 +29,7 @@ class Property extends PhpParser\BuilderAbstract
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePublic() {
|
||||
$this->setModifier(Stmt\Class_::MODIFIER_PUBLIC);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PUBLIC);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -39,7 +40,7 @@ class Property extends PhpParser\BuilderAbstract
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeProtected() {
|
||||
$this->setModifier(Stmt\Class_::MODIFIER_PROTECTED);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -50,7 +51,7 @@ class Property extends PhpParser\BuilderAbstract
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePrivate() {
|
||||
$this->setModifier(Stmt\Class_::MODIFIER_PRIVATE);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -61,7 +62,7 @@ class Property extends PhpParser\BuilderAbstract
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeStatic() {
|
||||
$this->setModifier(Stmt\Class_::MODIFIER_STATIC);
|
||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_STATIC);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -74,7 +75,7 @@ class Property extends PhpParser\BuilderAbstract
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function setDefault($value) {
|
||||
$this->default = $this->normalizeValue($value);
|
||||
$this->default = BuilderHelpers::normalizeValue($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -87,9 +88,9 @@ class Property extends PhpParser\BuilderAbstract
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function setDocComment($docComment) {
|
||||
$this->attributes = array(
|
||||
'comments' => array($this->normalizeDocComment($docComment))
|
||||
);
|
||||
$this->attributes = [
|
||||
'comments' => [BuilderHelpers::normalizeDocComment($docComment)]
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -99,13 +100,13 @@ class Property extends PhpParser\BuilderAbstract
|
||||
*
|
||||
* @return Stmt\Property The built property node
|
||||
*/
|
||||
public function getNode() {
|
||||
public function getNode() : PhpParser\Node {
|
||||
return new Stmt\Property(
|
||||
$this->flags !== 0 ? $this->flags : Stmt\Class_::MODIFIER_PUBLIC,
|
||||
array(
|
||||
[
|
||||
new Stmt\PropertyProperty($this->name, $this->default)
|
||||
),
|
||||
],
|
||||
$this->attributes
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,24 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Trait_ extends Declaration
|
||||
{
|
||||
protected $name;
|
||||
protected $properties = array();
|
||||
protected $methods = array();
|
||||
protected $uses = [];
|
||||
protected $properties = [];
|
||||
protected $methods = [];
|
||||
|
||||
/**
|
||||
* Creates an interface builder.
|
||||
*
|
||||
* @param string $name Name of the interface
|
||||
*/
|
||||
public function __construct($name) {
|
||||
public function __construct(string $name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
@ -28,12 +30,14 @@ class Trait_ extends Declaration
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function addStmt($stmt) {
|
||||
$stmt = $this->normalizeNode($stmt);
|
||||
$stmt = BuilderHelpers::normalizeNode($stmt);
|
||||
|
||||
if ($stmt instanceof Stmt\Property) {
|
||||
$this->properties[] = $stmt;
|
||||
} else if ($stmt instanceof Stmt\ClassMethod) {
|
||||
} elseif ($stmt instanceof Stmt\ClassMethod) {
|
||||
$this->methods[] = $stmt;
|
||||
} elseif ($stmt instanceof Stmt\TraitUse) {
|
||||
$this->uses[] = $stmt;
|
||||
} else {
|
||||
throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType()));
|
||||
}
|
||||
@ -46,11 +50,11 @@ class Trait_ extends Declaration
|
||||
*
|
||||
* @return Stmt\Trait_ The built interface node
|
||||
*/
|
||||
public function getNode() {
|
||||
public function getNode() : PhpParser\Node {
|
||||
return new Stmt\Trait_(
|
||||
$this->name, array(
|
||||
'stmts' => array_merge($this->properties, $this->methods)
|
||||
), $this->attributes
|
||||
$this->name, [
|
||||
'stmts' => array_merge($this->uses, $this->properties, $this->methods)
|
||||
], $this->attributes
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,14 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser\BuilderAbstract;
|
||||
use PhpParser\Builder;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
/**
|
||||
* @method $this as(string $alias) Sets alias for used name.
|
||||
*/
|
||||
class Use_ extends BuilderAbstract {
|
||||
class Use_ implements Builder
|
||||
{
|
||||
protected $name;
|
||||
protected $type;
|
||||
protected $alias = null;
|
||||
@ -20,8 +19,8 @@ class Use_ extends BuilderAbstract {
|
||||
* @param Node\Name|string $name Name of the entity (namespace, class, function, constant) to alias
|
||||
* @param int $type One of the Stmt\Use_::TYPE_* constants
|
||||
*/
|
||||
public function __construct($name, $type) {
|
||||
$this->name = $this->normalizeName($name);
|
||||
public function __construct($name, int $type) {
|
||||
$this->name = BuilderHelpers::normalizeName($name);
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
@ -32,27 +31,19 @@ class Use_ extends BuilderAbstract {
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
protected function as_($alias) {
|
||||
public function as(string $alias) {
|
||||
$this->alias = $alias;
|
||||
return $this;
|
||||
}
|
||||
public function __call($name, $args) {
|
||||
if (method_exists($this, $name . '_')) {
|
||||
return call_user_func_array(array($this, $name . '_'), $args);
|
||||
}
|
||||
|
||||
throw new \LogicException(sprintf('Method "%s" does not exist', $name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the built node.
|
||||
*
|
||||
* @return Node The built node
|
||||
*/
|
||||
public function getNode() {
|
||||
$alias = null !== $this->alias ? $this->alias : $this->name->getLast();
|
||||
return new Stmt\Use_(array(
|
||||
new Stmt\UseUse($this->name, $alias)
|
||||
), $this->type);
|
||||
public function getNode() : Node {
|
||||
return new Stmt\Use_([
|
||||
new Stmt\UseUse($this->name, $this->alias)
|
||||
], $this->type);
|
||||
}
|
||||
}
|
||||
|
@ -1,175 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
use PhpParser\Comment;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\Scalar;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
abstract class BuilderAbstract implements Builder {
|
||||
/**
|
||||
* Normalizes a node: Converts builder objects to nodes.
|
||||
*
|
||||
* @param Node|Builder $node The node to normalize
|
||||
*
|
||||
* @return Node The normalized node
|
||||
*/
|
||||
protected function normalizeNode($node) {
|
||||
if ($node instanceof Builder) {
|
||||
return $node->getNode();
|
||||
} elseif ($node instanceof Node) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
throw new \LogicException('Expected node or builder object');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a name: Converts plain string names to PhpParser\Node\Name.
|
||||
*
|
||||
* @param Name|string $name The name to normalize
|
||||
*
|
||||
* @return Name The normalized name
|
||||
*/
|
||||
protected function normalizeName($name) {
|
||||
if ($name instanceof Name) {
|
||||
return $name;
|
||||
} elseif (is_string($name)) {
|
||||
if (!$name) {
|
||||
throw new \LogicException('Name cannot be empty');
|
||||
}
|
||||
|
||||
if ($name[0] == '\\') {
|
||||
return new Name\FullyQualified(substr($name, 1));
|
||||
} elseif (0 === strpos($name, 'namespace\\')) {
|
||||
return new Name\Relative(substr($name, strlen('namespace\\')));
|
||||
} else {
|
||||
return new Name($name);
|
||||
}
|
||||
}
|
||||
|
||||
throw new \LogicException('Name must be a string or an instance of PhpParser\Node\Name');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a type: Converts plain-text type names into proper AST representation.
|
||||
*
|
||||
* In particular, builtin types are left as strings, custom types become Names and nullables
|
||||
* are wrapped in NullableType nodes.
|
||||
*
|
||||
* @param Name|string|NullableType $type The type to normalize
|
||||
*
|
||||
* @return Name|string|NullableType The normalized type
|
||||
*/
|
||||
protected function normalizeType($type) {
|
||||
if (!is_string($type)) {
|
||||
if (!$type instanceof Name && !$type instanceof NullableType) {
|
||||
throw new \LogicException(
|
||||
'Type must be a string, or an instance of Name or NullableType');
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
|
||||
$nullable = false;
|
||||
if (strlen($type) > 0 && $type[0] === '?') {
|
||||
$nullable = true;
|
||||
$type = substr($type, 1);
|
||||
}
|
||||
|
||||
$builtinTypes = array(
|
||||
'array', 'callable', 'string', 'int', 'float', 'bool', 'iterable', 'void'
|
||||
);
|
||||
|
||||
$lowerType = strtolower($type);
|
||||
if (in_array($lowerType, $builtinTypes)) {
|
||||
$type = $lowerType;
|
||||
} else {
|
||||
$type = $this->normalizeName($type);
|
||||
}
|
||||
|
||||
if ($nullable && $type === 'void') {
|
||||
throw new \LogicException('void type cannot be nullable');
|
||||
}
|
||||
|
||||
return $nullable ? new Node\NullableType($type) : $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a value: Converts nulls, booleans, integers,
|
||||
* floats, strings and arrays into their respective nodes
|
||||
*
|
||||
* @param mixed $value The value to normalize
|
||||
*
|
||||
* @return Expr The normalized value
|
||||
*/
|
||||
protected function normalizeValue($value) {
|
||||
if ($value instanceof Node) {
|
||||
return $value;
|
||||
} elseif (is_null($value)) {
|
||||
return new Expr\ConstFetch(
|
||||
new Name('null')
|
||||
);
|
||||
} elseif (is_bool($value)) {
|
||||
return new Expr\ConstFetch(
|
||||
new Name($value ? 'true' : 'false')
|
||||
);
|
||||
} elseif (is_int($value)) {
|
||||
return new Scalar\LNumber($value);
|
||||
} elseif (is_float($value)) {
|
||||
return new Scalar\DNumber($value);
|
||||
} elseif (is_string($value)) {
|
||||
return new Scalar\String_($value);
|
||||
} elseif (is_array($value)) {
|
||||
$items = array();
|
||||
$lastKey = -1;
|
||||
foreach ($value as $itemKey => $itemValue) {
|
||||
// for consecutive, numeric keys don't generate keys
|
||||
if (null !== $lastKey && ++$lastKey === $itemKey) {
|
||||
$items[] = new Expr\ArrayItem(
|
||||
$this->normalizeValue($itemValue)
|
||||
);
|
||||
} else {
|
||||
$lastKey = null;
|
||||
$items[] = new Expr\ArrayItem(
|
||||
$this->normalizeValue($itemValue),
|
||||
$this->normalizeValue($itemKey)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new Expr\Array_($items);
|
||||
} else {
|
||||
throw new \LogicException('Invalid value');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a doc comment: Converts plain strings to PhpParser\Comment\Doc.
|
||||
*
|
||||
* @param Comment\Doc|string $docComment The doc comment to normalize
|
||||
*
|
||||
* @return Comment\Doc The normalized doc comment
|
||||
*/
|
||||
protected function normalizeDocComment($docComment) {
|
||||
if ($docComment instanceof Comment\Doc) {
|
||||
return $docComment;
|
||||
} else if (is_string($docComment)) {
|
||||
return new Comment\Doc($docComment);
|
||||
} else {
|
||||
throw new \LogicException('Doc comment must be a string or an instance of PhpParser\Comment\Doc');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a modifier in the $this->type property.
|
||||
*
|
||||
* @param int $modifier Modifier to set
|
||||
*/
|
||||
protected function setModifier($modifier) {
|
||||
Stmt\Class_::verifyModifier($this->flags, $modifier);
|
||||
$this->flags |= $modifier;
|
||||
}
|
||||
}
|
@ -1,21 +1,16 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
use PhpParser\Builder;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\BinaryOp\Concat;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\Stmt\Use_;
|
||||
|
||||
/**
|
||||
* The following methods use reserved keywords, so their implementation is defined with an underscore and made available
|
||||
* with the reserved name through __call() magic.
|
||||
*
|
||||
* @method Builder\Namespace_ namespace(string $name) Creates a namespace builder.
|
||||
* @method Builder\Class_ class(string $name) Creates a class builder.
|
||||
* @method Builder\Interface_ interface(string $name) Creates an interface builder.
|
||||
* @method Builder\Trait_ trait(string $name) Creates a trait builder.
|
||||
* @method Builder\Function_ function(string $name) Creates a function builder.
|
||||
* @method Builder\Use_ use(string $name) Creates a namespace/class use builder.
|
||||
*/
|
||||
class BuilderFactory
|
||||
{
|
||||
/**
|
||||
@ -25,7 +20,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\Namespace_ The created namespace builder
|
||||
*/
|
||||
protected function _namespace($name) {
|
||||
public function namespace($name) : Builder\Namespace_ {
|
||||
return new Builder\Namespace_($name);
|
||||
}
|
||||
|
||||
@ -36,7 +31,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\Class_ The created class builder
|
||||
*/
|
||||
protected function _class($name) {
|
||||
public function class(string $name) : Builder\Class_ {
|
||||
return new Builder\Class_($name);
|
||||
}
|
||||
|
||||
@ -47,7 +42,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\Interface_ The created interface builder
|
||||
*/
|
||||
protected function _interface($name) {
|
||||
public function interface(string $name) : Builder\Interface_ {
|
||||
return new Builder\Interface_($name);
|
||||
}
|
||||
|
||||
@ -58,7 +53,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\Trait_ The created trait builder
|
||||
*/
|
||||
protected function _trait($name) {
|
||||
public function trait(string $name) : Builder\Trait_ {
|
||||
return new Builder\Trait_($name);
|
||||
}
|
||||
|
||||
@ -69,7 +64,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\Method The created method builder
|
||||
*/
|
||||
public function method($name) {
|
||||
public function method(string $name) : Builder\Method {
|
||||
return new Builder\Method($name);
|
||||
}
|
||||
|
||||
@ -80,7 +75,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\Param The created parameter builder
|
||||
*/
|
||||
public function param($name) {
|
||||
public function param(string $name) : Builder\Param {
|
||||
return new Builder\Param($name);
|
||||
}
|
||||
|
||||
@ -91,7 +86,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\Property The created property builder
|
||||
*/
|
||||
public function property($name) {
|
||||
public function property(string $name) : Builder\Property {
|
||||
return new Builder\Property($name);
|
||||
}
|
||||
|
||||
@ -102,7 +97,7 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\Function_ The created function builder
|
||||
*/
|
||||
protected function _function($name) {
|
||||
public function function(string $name) : Builder\Function_ {
|
||||
return new Builder\Function_($name);
|
||||
}
|
||||
|
||||
@ -113,15 +108,165 @@ class BuilderFactory
|
||||
*
|
||||
* @return Builder\Use_ The create use builder
|
||||
*/
|
||||
protected function _use($name) {
|
||||
public function use($name) : Builder\Use_ {
|
||||
return new Builder\Use_($name, Use_::TYPE_NORMAL);
|
||||
}
|
||||
|
||||
public function __call($name, array $args) {
|
||||
if (method_exists($this, '_' . $name)) {
|
||||
return call_user_func_array(array($this, '_' . $name), $args);
|
||||
/**
|
||||
* Creates node a for a literal value.
|
||||
*
|
||||
* @param Expr|bool|null|int|float|string|array $value $value
|
||||
*
|
||||
* @return Expr
|
||||
*/
|
||||
public function val($value) : Expr {
|
||||
return BuilderHelpers::normalizeValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes an argument list.
|
||||
*
|
||||
* Creates Arg nodes for all arguments and converts literal values to expressions.
|
||||
*
|
||||
* @param array $args List of arguments to normalize
|
||||
*
|
||||
* @return Arg[]
|
||||
*/
|
||||
public function args(array $args) : array {
|
||||
$normalizedArgs = [];
|
||||
foreach ($args as $arg) {
|
||||
if ($arg instanceof Arg) {
|
||||
$normalizedArgs[] = $arg;
|
||||
} else {
|
||||
$normalizedArgs[] = new Arg(BuilderHelpers::normalizeValue($arg));
|
||||
}
|
||||
}
|
||||
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 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.
|
||||
*
|
||||
* @param Expr|string ...$exprs Expressions or literal strings
|
||||
*
|
||||
* @return Concat
|
||||
*/
|
||||
public function concat(...$exprs) : Concat {
|
||||
$numExprs = count($exprs);
|
||||
if ($numExprs < 2) {
|
||||
throw new \LogicException('Expected at least two expressions');
|
||||
}
|
||||
|
||||
throw new \LogicException(sprintf('Method "%s" does not exist', $name));
|
||||
$lastConcat = $this->normalizeStringExpr($exprs[0]);
|
||||
for ($i = 1; $i < $numExprs; $i++) {
|
||||
$lastConcat = new Concat($lastConcat, $this->normalizeStringExpr($exprs[$i]));
|
||||
}
|
||||
return $lastConcat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|Expr $expr
|
||||
* @return Expr
|
||||
*/
|
||||
private function normalizeStringExpr($expr) : Expr {
|
||||
if ($expr instanceof Expr) {
|
||||
return $expr;
|
||||
}
|
||||
|
||||
if (\is_string($expr)) {
|
||||
return new String_($expr);
|
||||
}
|
||||
|
||||
throw new \LogicException('Expected string or Expr');
|
||||
}
|
||||
}
|
||||
|
277
lib/PhpParser/BuilderHelpers.php
Normal file
277
lib/PhpParser/BuilderHelpers.php
Normal file
@ -0,0 +1,277 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\Scalar;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
/**
|
||||
* This class defines helpers used in the implementation of builders. Don't use it directly.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class BuilderHelpers
|
||||
{
|
||||
/**
|
||||
* Normalizes a node: Converts builder objects to nodes.
|
||||
*
|
||||
* @param Node|Builder $node The node to normalize
|
||||
*
|
||||
* @return Node The normalized node
|
||||
*/
|
||||
public static function normalizeNode($node) : Node {
|
||||
if ($node instanceof Builder) {
|
||||
return $node->getNode();
|
||||
} elseif ($node instanceof Node) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
throw new \LogicException('Expected node or builder object');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a node to a statement.
|
||||
*
|
||||
* Expressions are wrapped in a Stmt\Expression node.
|
||||
*
|
||||
* @param Node|Builder $node The node to normalize
|
||||
*
|
||||
* @return Stmt The normalized statement node
|
||||
*/
|
||||
public static function normalizeStmt($node) : Stmt {
|
||||
$node = self::normalizeNode($node);
|
||||
if ($node instanceof Stmt) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
if ($node instanceof Expr) {
|
||||
return new Stmt\Expression($node);
|
||||
}
|
||||
|
||||
throw new \LogicException('Expected statement or expression node');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes strings to Identifier.
|
||||
*
|
||||
* @param string|Identifier $name The identifier to normalize
|
||||
*
|
||||
* @return Identifier The normalized identifier
|
||||
*/
|
||||
public static function normalizeIdentifier($name) : Identifier {
|
||||
if ($name instanceof Identifier) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
if (\is_string($name)) {
|
||||
return new Identifier($name);
|
||||
}
|
||||
|
||||
throw new \LogicException('Expected string or instance of Node\Identifier or Node\Expr');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes strings to Identifier, also allowing expressions.
|
||||
*
|
||||
* @param string|Identifier|Expr $name The identifier to normalize
|
||||
*
|
||||
* @return Identifier|Expr The normalized identifier or expression
|
||||
*/
|
||||
public static function normalizeIdentifierOrExpr($name) {
|
||||
if ($name instanceof Identifier || $name instanceof Expr) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
if (\is_string($name)) {
|
||||
return new Identifier($name);
|
||||
}
|
||||
|
||||
throw new \LogicException('Expected string or instance of Node\Identifier or Node\Expr');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a name: Converts string names to Name nodes.
|
||||
*
|
||||
* @param Name|string $name The name to normalize
|
||||
*
|
||||
* @return Name The normalized name
|
||||
*/
|
||||
public static function normalizeName($name) : Name {
|
||||
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) {
|
||||
return $name;
|
||||
} elseif (is_string($name)) {
|
||||
if (!$name) {
|
||||
throw new \LogicException('Name cannot be empty');
|
||||
}
|
||||
|
||||
if ($name[0] === '\\') {
|
||||
return new Name\FullyQualified(substr($name, 1));
|
||||
} elseif (0 === strpos($name, 'namespace\\')) {
|
||||
return new Name\Relative(substr($name, strlen('namespace\\')));
|
||||
} else {
|
||||
return new Name($name);
|
||||
}
|
||||
}
|
||||
|
||||
if ($allowExpr) {
|
||||
if ($name instanceof Expr) {
|
||||
return $name;
|
||||
}
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a type: Converts plain-text type names into proper AST representation.
|
||||
*
|
||||
* In particular, builtin types become Identifiers, custom types become Names and nullables
|
||||
* are wrapped in NullableType nodes.
|
||||
*
|
||||
* @param string|Name|Identifier|NullableType $type The type to normalize
|
||||
*
|
||||
* @return Name|Identifier|NullableType The normalized type
|
||||
*/
|
||||
public static function normalizeType($type) {
|
||||
if (!is_string($type)) {
|
||||
if (!$type instanceof Name && !$type instanceof Identifier
|
||||
&& !$type instanceof NullableType) {
|
||||
throw new \LogicException(
|
||||
'Type must be a string, or an instance of Name, Identifier or NullableType');
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
|
||||
$nullable = false;
|
||||
if (strlen($type) > 0 && $type[0] === '?') {
|
||||
$nullable = true;
|
||||
$type = substr($type, 1);
|
||||
}
|
||||
|
||||
$builtinTypes = [
|
||||
'array', 'callable', 'string', 'int', 'float', 'bool', 'iterable', 'void', 'object'
|
||||
];
|
||||
|
||||
$lowerType = strtolower($type);
|
||||
if (in_array($lowerType, $builtinTypes)) {
|
||||
$type = new Identifier($lowerType);
|
||||
} else {
|
||||
$type = self::normalizeName($type);
|
||||
}
|
||||
|
||||
if ($nullable && (string) $type === 'void') {
|
||||
throw new \LogicException('void type cannot be nullable');
|
||||
}
|
||||
|
||||
return $nullable ? new Node\NullableType($type) : $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a value: Converts nulls, booleans, integers,
|
||||
* floats, strings and arrays into their respective nodes
|
||||
*
|
||||
* @param Node\Expr|bool|null|int|float|string|array $value The value to normalize
|
||||
*
|
||||
* @return Expr The normalized value
|
||||
*/
|
||||
public static function normalizeValue($value) : Expr {
|
||||
if ($value instanceof Node\Expr) {
|
||||
return $value;
|
||||
} elseif (is_null($value)) {
|
||||
return new Expr\ConstFetch(
|
||||
new Name('null')
|
||||
);
|
||||
} elseif (is_bool($value)) {
|
||||
return new Expr\ConstFetch(
|
||||
new Name($value ? 'true' : 'false')
|
||||
);
|
||||
} elseif (is_int($value)) {
|
||||
return new Scalar\LNumber($value);
|
||||
} elseif (is_float($value)) {
|
||||
return new Scalar\DNumber($value);
|
||||
} elseif (is_string($value)) {
|
||||
return new Scalar\String_($value);
|
||||
} elseif (is_array($value)) {
|
||||
$items = [];
|
||||
$lastKey = -1;
|
||||
foreach ($value as $itemKey => $itemValue) {
|
||||
// for consecutive, numeric keys don't generate keys
|
||||
if (null !== $lastKey && ++$lastKey === $itemKey) {
|
||||
$items[] = new Expr\ArrayItem(
|
||||
self::normalizeValue($itemValue)
|
||||
);
|
||||
} else {
|
||||
$lastKey = null;
|
||||
$items[] = new Expr\ArrayItem(
|
||||
self::normalizeValue($itemValue),
|
||||
self::normalizeValue($itemKey)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new Expr\Array_($items);
|
||||
} else {
|
||||
throw new \LogicException('Invalid value');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a doc comment: Converts plain strings to PhpParser\Comment\Doc.
|
||||
*
|
||||
* @param Comment\Doc|string $docComment The doc comment to normalize
|
||||
*
|
||||
* @return Comment\Doc The normalized doc comment
|
||||
*/
|
||||
public static function normalizeDocComment($docComment) : Comment\Doc {
|
||||
if ($docComment instanceof Comment\Doc) {
|
||||
return $docComment;
|
||||
} elseif (is_string($docComment)) {
|
||||
return new Comment\Doc($docComment);
|
||||
} else {
|
||||
throw new \LogicException('Doc comment must be a string or an instance of PhpParser\Comment\Doc');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a modifier and returns new modifier bitmask.
|
||||
*
|
||||
* @param int $modifiers Existing modifiers
|
||||
* @param int $modifier Modifier to set
|
||||
*
|
||||
* @return int New modifiers
|
||||
*/
|
||||
public static function addModifier(int $modifiers, int $modifier) : int {
|
||||
Stmt\Class_::verifyModifier($modifiers, $modifier);
|
||||
return $modifiers | $modifier;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
@ -7,18 +7,23 @@ class Comment implements \JsonSerializable
|
||||
protected $text;
|
||||
protected $line;
|
||||
protected $filePos;
|
||||
protected $tokenPos;
|
||||
|
||||
/**
|
||||
* Constructs a comment node.
|
||||
*
|
||||
* @param string $text Comment text (including comment delimiters like /*)
|
||||
* @param int $startLine Line number the comment started on
|
||||
* @param int $startFilePos File offset the comment started on
|
||||
* @param string $text Comment text (including comment delimiters like /*)
|
||||
* @param int $startLine Line number the comment started on
|
||||
* @param int $startFilePos File offset the comment started on
|
||||
* @param int $startTokenPos Token offset the comment started on
|
||||
*/
|
||||
public function __construct($text, $startLine = -1, $startFilePos = -1) {
|
||||
public function __construct(
|
||||
string $text, int $startLine = -1, int $startFilePos = -1, int $startTokenPos = -1
|
||||
) {
|
||||
$this->text = $text;
|
||||
$this->line = $startLine;
|
||||
$this->filePos = $startFilePos;
|
||||
$this->tokenPos = $startTokenPos;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -26,7 +31,7 @@ class Comment implements \JsonSerializable
|
||||
*
|
||||
* @return string The comment text (including comment delimiters like /*)
|
||||
*/
|
||||
public function getText() {
|
||||
public function getText() : string {
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
@ -35,7 +40,7 @@ class Comment implements \JsonSerializable
|
||||
*
|
||||
* @return int Line number
|
||||
*/
|
||||
public function getLine() {
|
||||
public function getLine() : int {
|
||||
return $this->line;
|
||||
}
|
||||
|
||||
@ -44,16 +49,25 @@ class Comment implements \JsonSerializable
|
||||
*
|
||||
* @return int File offset
|
||||
*/
|
||||
public function getFilePos() {
|
||||
public function getFilePos() : int {
|
||||
return $this->filePos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the token offset the comment started on.
|
||||
*
|
||||
* @return int Token offset
|
||||
*/
|
||||
public function getTokenPos() : int {
|
||||
return $this->tokenPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the comment text.
|
||||
*
|
||||
* @return string The comment text (including comment delimiters like /*)
|
||||
*/
|
||||
public function __toString() {
|
||||
public function __toString() : string {
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
@ -114,9 +128,17 @@ class Comment implements \JsonSerializable
|
||||
return $text;
|
||||
}
|
||||
|
||||
private function getShortestWhitespacePrefixLen($str) {
|
||||
/**
|
||||
* Get length of shortest whitespace prefix (at the start of a line).
|
||||
*
|
||||
* If there is a line with no prefix whitespace, 0 is a valid return value.
|
||||
*
|
||||
* @param string $str String to check
|
||||
* @return int Length in characters. Tabs count as single characters.
|
||||
*/
|
||||
private function getShortestWhitespacePrefixLen(string $str) : int {
|
||||
$lines = explode("\n", $str);
|
||||
$shortestPrefixLen = INF;
|
||||
$shortestPrefixLen = \INF;
|
||||
foreach ($lines as $line) {
|
||||
preg_match('(^\s*)', $line, $matches);
|
||||
$prefixLen = strlen($matches[0]);
|
||||
@ -127,7 +149,11 @@ class Comment implements \JsonSerializable
|
||||
return $shortestPrefixLen;
|
||||
}
|
||||
|
||||
public function jsonSerialize() {
|
||||
/**
|
||||
* @return array
|
||||
* @psalm-return array{nodeType:string, text:mixed, line:mixed, filePos:mixed}
|
||||
*/
|
||||
public function jsonSerialize() : array {
|
||||
// Technically not a node, but we make it look like one anyway
|
||||
$type = $this instanceof Comment\Doc ? 'Comment_Doc' : 'Comment';
|
||||
return [
|
||||
@ -135,6 +161,7 @@ class Comment implements \JsonSerializable
|
||||
'text' => $this->text,
|
||||
'line' => $this->line,
|
||||
'filePos' => $this->filePos,
|
||||
'tokenPos' => $this->tokenPos,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Comment;
|
||||
|
||||
class Doc extends \PhpParser\Comment
|
||||
{
|
||||
}
|
||||
}
|
||||
|
6
lib/PhpParser/ConstExprEvaluationException.php
Normal file
6
lib/PhpParser/ConstExprEvaluationException.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
class ConstExprEvaluationException extends \Exception
|
||||
{}
|
226
lib/PhpParser/ConstExprEvaluator.php
Normal file
226
lib/PhpParser/ConstExprEvaluator.php
Normal file
@ -0,0 +1,226 @@
|
||||
<?php
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Scalar;
|
||||
|
||||
/**
|
||||
* Evaluates constant expressions.
|
||||
*
|
||||
* This evaluator is able to evaluate all constant expressions (as defined by PHP), which can be
|
||||
* evaluated without further context. If a subexpression is not of this type, a user-provided
|
||||
* fallback evaluator is invoked. To support all constant expressions that are also supported by
|
||||
* PHP (and not already handled by this class), the fallback evaluator must be able to handle the
|
||||
* following node types:
|
||||
*
|
||||
* * All Scalar\MagicConst\* nodes.
|
||||
* * Expr\ConstFetch nodes. Only null/false/true are already handled by this class.
|
||||
* * Expr\ClassConstFetch nodes.
|
||||
*
|
||||
* The fallback evaluator should throw ConstExprEvaluationException for nodes it cannot evaluate.
|
||||
*
|
||||
* The evaluation is dependent on runtime configuration in two respects: Firstly, floating
|
||||
* point to string conversions are affected by the precision ini setting. Secondly, they are also
|
||||
* affected by the LC_NUMERIC locale.
|
||||
*/
|
||||
class ConstExprEvaluator
|
||||
{
|
||||
private $fallbackEvaluator;
|
||||
|
||||
/**
|
||||
* Create a constant expression evaluator.
|
||||
*
|
||||
* The provided fallback evaluator is invoked whenever a subexpression cannot be evaluated. See
|
||||
* class doc comment for more information.
|
||||
*
|
||||
* @param callable|null $fallbackEvaluator To call if subexpression cannot be evaluated
|
||||
*/
|
||||
public function __construct(callable $fallbackEvaluator = null) {
|
||||
$this->fallbackEvaluator = $fallbackEvaluator ?? function(Expr $expr) {
|
||||
throw new ConstExprEvaluationException(
|
||||
"Expression of type {$expr->getType()} cannot be evaluated"
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Silently evaluates a constant expression into a PHP value.
|
||||
*
|
||||
* Thrown Errors, warnings or notices will be converted into a ConstExprEvaluationException.
|
||||
* The original source of the exception is available through getPrevious().
|
||||
*
|
||||
* If some part of the expression cannot be evaluated, the fallback evaluator passed to the
|
||||
* constructor will be invoked. By default, if no fallback is provided, an exception of type
|
||||
* ConstExprEvaluationException is thrown.
|
||||
*
|
||||
* See class doc comment for caveats and limitations.
|
||||
*
|
||||
* @param Expr $expr Constant expression to evaluate
|
||||
* @return mixed Result of evaluation
|
||||
*
|
||||
* @throws ConstExprEvaluationException if the expression cannot be evaluated or an error occurred
|
||||
*/
|
||||
public function evaluateSilently(Expr $expr) {
|
||||
set_error_handler(function($num, $str, $file, $line) {
|
||||
throw new \ErrorException($str, 0, $num, $file, $line);
|
||||
});
|
||||
|
||||
try {
|
||||
return $this->evaluate($expr);
|
||||
} catch (\Throwable $e) {
|
||||
if (!$e instanceof ConstExprEvaluationException) {
|
||||
$e = new ConstExprEvaluationException(
|
||||
"An error occurred during constant expression evaluation", 0, $e);
|
||||
}
|
||||
throw $e;
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly evaluates a constant expression into a PHP value.
|
||||
*
|
||||
* May generate Error exceptions, warnings or notices. Use evaluateSilently() to convert these
|
||||
* into a ConstExprEvaluationException.
|
||||
*
|
||||
* If some part of the expression cannot be evaluated, the fallback evaluator passed to the
|
||||
* constructor will be invoked. By default, if no fallback is provided, an exception of type
|
||||
* ConstExprEvaluationException is thrown.
|
||||
*
|
||||
* See class doc comment for caveats and limitations.
|
||||
*
|
||||
* @param Expr $expr Constant expression to evaluate
|
||||
* @return mixed Result of evaluation
|
||||
*
|
||||
* @throws ConstExprEvaluationException if the expression cannot be evaluated
|
||||
*/
|
||||
public function evaluateDirectly(Expr $expr) {
|
||||
return $this->evaluate($expr);
|
||||
}
|
||||
|
||||
private function evaluate(Expr $expr) {
|
||||
if ($expr instanceof Scalar\LNumber
|
||||
|| $expr instanceof Scalar\DNumber
|
||||
|| $expr instanceof Scalar\String_
|
||||
) {
|
||||
return $expr->value;
|
||||
}
|
||||
|
||||
if ($expr instanceof Expr\Array_) {
|
||||
return $this->evaluateArray($expr);
|
||||
}
|
||||
|
||||
// Unary operators
|
||||
if ($expr instanceof Expr\UnaryPlus) {
|
||||
return +$this->evaluate($expr->expr);
|
||||
}
|
||||
if ($expr instanceof Expr\UnaryMinus) {
|
||||
return -$this->evaluate($expr->expr);
|
||||
}
|
||||
if ($expr instanceof Expr\BooleanNot) {
|
||||
return !$this->evaluate($expr->expr);
|
||||
}
|
||||
if ($expr instanceof Expr\BitwiseNot) {
|
||||
return ~$this->evaluate($expr->expr);
|
||||
}
|
||||
|
||||
if ($expr instanceof Expr\BinaryOp) {
|
||||
return $this->evaluateBinaryOp($expr);
|
||||
}
|
||||
|
||||
if ($expr instanceof Expr\Ternary) {
|
||||
return $this->evaluateTernary($expr);
|
||||
}
|
||||
|
||||
if ($expr instanceof Expr\ArrayDimFetch && null !== $expr->dim) {
|
||||
return $this->evaluate($expr->var)[$this->evaluate($expr->dim)];
|
||||
}
|
||||
|
||||
if ($expr instanceof Expr\ConstFetch) {
|
||||
return $this->evaluateConstFetch($expr);
|
||||
}
|
||||
|
||||
return ($this->fallbackEvaluator)($expr);
|
||||
}
|
||||
|
||||
private function evaluateArray(Expr\Array_ $expr) {
|
||||
$array = [];
|
||||
foreach ($expr->items as $item) {
|
||||
if (null !== $item->key) {
|
||||
$array[$this->evaluate($item->key)] = $this->evaluate($item->value);
|
||||
} else {
|
||||
$array[] = $this->evaluate($item->value);
|
||||
}
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
private function evaluateTernary(Expr\Ternary $expr) {
|
||||
if (null === $expr->if) {
|
||||
return $this->evaluate($expr->cond) ?: $this->evaluate($expr->else);
|
||||
}
|
||||
|
||||
return $this->evaluate($expr->cond)
|
||||
? $this->evaluate($expr->if)
|
||||
: $this->evaluate($expr->else);
|
||||
}
|
||||
|
||||
private function evaluateBinaryOp(Expr\BinaryOp $expr) {
|
||||
if ($expr instanceof Expr\BinaryOp\Coalesce
|
||||
&& $expr->left instanceof Expr\ArrayDimFetch
|
||||
) {
|
||||
// This needs to be special cased to respect BP_VAR_IS fetch semantics
|
||||
return $this->evaluate($expr->left->var)[$this->evaluate($expr->left->dim)]
|
||||
?? $this->evaluate($expr->right);
|
||||
}
|
||||
|
||||
// The evaluate() calls are repeated in each branch, because some of the operators are
|
||||
// short-circuiting and evaluating the RHS in advance may be illegal in that case
|
||||
$l = $expr->left;
|
||||
$r = $expr->right;
|
||||
switch ($expr->getOperatorSigil()) {
|
||||
case '&': return $this->evaluate($l) & $this->evaluate($r);
|
||||
case '|': return $this->evaluate($l) | $this->evaluate($r);
|
||||
case '^': return $this->evaluate($l) ^ $this->evaluate($r);
|
||||
case '&&': return $this->evaluate($l) && $this->evaluate($r);
|
||||
case '||': return $this->evaluate($l) || $this->evaluate($r);
|
||||
case '??': return $this->evaluate($l) ?? $this->evaluate($r);
|
||||
case '.': return $this->evaluate($l) . $this->evaluate($r);
|
||||
case '/': return $this->evaluate($l) / $this->evaluate($r);
|
||||
case '==': return $this->evaluate($l) == $this->evaluate($r);
|
||||
case '>': return $this->evaluate($l) > $this->evaluate($r);
|
||||
case '>=': return $this->evaluate($l) >= $this->evaluate($r);
|
||||
case '===': return $this->evaluate($l) === $this->evaluate($r);
|
||||
case 'and': return $this->evaluate($l) and $this->evaluate($r);
|
||||
case 'or': return $this->evaluate($l) or $this->evaluate($r);
|
||||
case 'xor': return $this->evaluate($l) xor $this->evaluate($r);
|
||||
case '-': return $this->evaluate($l) - $this->evaluate($r);
|
||||
case '%': return $this->evaluate($l) % $this->evaluate($r);
|
||||
case '*': return $this->evaluate($l) * $this->evaluate($r);
|
||||
case '!=': return $this->evaluate($l) != $this->evaluate($r);
|
||||
case '!==': return $this->evaluate($l) !== $this->evaluate($r);
|
||||
case '+': return $this->evaluate($l) + $this->evaluate($r);
|
||||
case '**': return $this->evaluate($l) ** $this->evaluate($r);
|
||||
case '<<': return $this->evaluate($l) << $this->evaluate($r);
|
||||
case '>>': return $this->evaluate($l) >> $this->evaluate($r);
|
||||
case '<': return $this->evaluate($l) < $this->evaluate($r);
|
||||
case '<=': return $this->evaluate($l) <= $this->evaluate($r);
|
||||
case '<=>': return $this->evaluate($l) <=> $this->evaluate($r);
|
||||
}
|
||||
|
||||
throw new \Exception('Should not happen');
|
||||
}
|
||||
|
||||
private function evaluateConstFetch(Expr\ConstFetch $expr) {
|
||||
$name = $expr->name->toLowerString();
|
||||
switch ($name) {
|
||||
case 'null': return null;
|
||||
case 'false': return false;
|
||||
case 'true': return true;
|
||||
}
|
||||
|
||||
return ($this->fallbackEvaluator)($expr);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
@ -14,12 +14,12 @@ class Error extends \RuntimeException
|
||||
* @param array|int $attributes Attributes of node/token where error occurred
|
||||
* (or start line of error -- deprecated)
|
||||
*/
|
||||
public function __construct($message, $attributes = array()) {
|
||||
public function __construct(string $message, $attributes = []) {
|
||||
$this->rawMessage = (string) $message;
|
||||
if (is_array($attributes)) {
|
||||
$this->attributes = $attributes;
|
||||
} else {
|
||||
$this->attributes = array('startLine' => $attributes);
|
||||
$this->attributes = ['startLine' => $attributes];
|
||||
}
|
||||
$this->updateMessage();
|
||||
}
|
||||
@ -29,7 +29,7 @@ class Error extends \RuntimeException
|
||||
*
|
||||
* @return string Error message
|
||||
*/
|
||||
public function getRawMessage() {
|
||||
public function getRawMessage() : string {
|
||||
return $this->rawMessage;
|
||||
}
|
||||
|
||||
@ -38,8 +38,8 @@ class Error extends \RuntimeException
|
||||
*
|
||||
* @return int Error start line
|
||||
*/
|
||||
public function getStartLine() {
|
||||
return isset($this->attributes['startLine']) ? $this->attributes['startLine'] : -1;
|
||||
public function getStartLine() : int {
|
||||
return $this->attributes['startLine'] ?? -1;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -47,22 +47,21 @@ class Error extends \RuntimeException
|
||||
*
|
||||
* @return int Error end line
|
||||
*/
|
||||
public function getEndLine() {
|
||||
return isset($this->attributes['endLine']) ? $this->attributes['endLine'] : -1;
|
||||
public function getEndLine() : int {
|
||||
return $this->attributes['endLine'] ?? -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the attributes of the node/token the error occurred at.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAttributes() {
|
||||
public function getAttributes() : array {
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@ -76,7 +75,7 @@ class Error extends \RuntimeException
|
||||
*
|
||||
* @param string $message Error message
|
||||
*/
|
||||
public function setRawMessage($message) {
|
||||
public function setRawMessage(string $message) {
|
||||
$this->rawMessage = (string) $message;
|
||||
$this->updateMessage();
|
||||
}
|
||||
@ -86,7 +85,7 @@ class Error extends \RuntimeException
|
||||
*
|
||||
* @param int $line Error start line
|
||||
*/
|
||||
public function setStartLine($line) {
|
||||
public function setStartLine(int $line) {
|
||||
$this->attributes['startLine'] = (int) $line;
|
||||
$this->updateMessage();
|
||||
}
|
||||
@ -98,8 +97,8 @@ class Error extends \RuntimeException
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasColumnInfo() {
|
||||
return isset($this->attributes['startFilePos']) && isset($this->attributes['endFilePos']);
|
||||
public function hasColumnInfo() : bool {
|
||||
return isset($this->attributes['startFilePos'], $this->attributes['endFilePos']);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -108,7 +107,7 @@ class Error extends \RuntimeException
|
||||
* @param string $code Source code of the file
|
||||
* @return int
|
||||
*/
|
||||
public function getStartColumn($code) {
|
||||
public function getStartColumn(string $code) : int {
|
||||
if (!$this->hasColumnInfo()) {
|
||||
throw new \RuntimeException('Error does not have column information');
|
||||
}
|
||||
@ -122,7 +121,7 @@ class Error extends \RuntimeException
|
||||
* @param string $code Source code of the file
|
||||
* @return int
|
||||
*/
|
||||
public function getEndColumn($code) {
|
||||
public function getEndColumn(string $code) : int {
|
||||
if (!$this->hasColumnInfo()) {
|
||||
throw new \RuntimeException('Error does not have column information');
|
||||
}
|
||||
@ -130,7 +129,14 @@ class Error extends \RuntimeException
|
||||
return $this->toColumn($code, $this->attributes['endFilePos']);
|
||||
}
|
||||
|
||||
public function getMessageWithColumnInfo($code) {
|
||||
/**
|
||||
* Formats message including line and column information.
|
||||
*
|
||||
* @param string $code Source code associated with the error, for calculation of the columns
|
||||
*
|
||||
* @return string Formatted message
|
||||
*/
|
||||
public function getMessageWithColumnInfo(string $code) : string {
|
||||
return sprintf(
|
||||
'%s from %d:%d to %d:%d', $this->getRawMessage(),
|
||||
$this->getStartLine(), $this->getStartColumn($code),
|
||||
@ -138,7 +144,15 @@ class Error extends \RuntimeException
|
||||
);
|
||||
}
|
||||
|
||||
private function toColumn($code, $pos) {
|
||||
/**
|
||||
* Converts a file offset into a column.
|
||||
*
|
||||
* @param string $code Source code that $pos indexes into
|
||||
* @param int $pos 0-based position in $code
|
||||
*
|
||||
* @return int 1-based column (relative to start of line)
|
||||
*/
|
||||
private function toColumn(string $code, int $pos) : int {
|
||||
if ($pos > strlen($code)) {
|
||||
throw new \RuntimeException('Invalid position information');
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
@ -10,4 +10,4 @@ interface ErrorHandler
|
||||
* @param Error $error The error that needs to be handled
|
||||
*/
|
||||
public function handleError(Error $error);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\ErrorHandler;
|
||||
|
||||
@ -24,7 +24,7 @@ class Collecting implements ErrorHandler
|
||||
*
|
||||
* @return Error[]
|
||||
*/
|
||||
public function getErrors() {
|
||||
public function getErrors() : array {
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ class Collecting implements ErrorHandler
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasErrors() {
|
||||
public function hasErrors() : bool {
|
||||
return !empty($this->errors);
|
||||
}
|
||||
|
||||
@ -43,4 +43,4 @@ class Collecting implements ErrorHandler
|
||||
public function clearErrors() {
|
||||
$this->errors = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\ErrorHandler;
|
||||
|
||||
@ -15,4 +15,4 @@ class Throwing implements ErrorHandler
|
||||
public function handleError(Error $error) {
|
||||
throw $error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
27
lib/PhpParser/Internal/DiffElem.php
Normal file
27
lib/PhpParser/Internal/DiffElem.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Internal;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class DiffElem
|
||||
{
|
||||
const TYPE_KEEP = 0;
|
||||
const TYPE_REMOVE = 1;
|
||||
const TYPE_ADD = 2;
|
||||
const TYPE_REPLACE = 3;
|
||||
|
||||
/** @var int One of the TYPE_* constants */
|
||||
public $type;
|
||||
/** @var mixed Is null for add operations */
|
||||
public $old;
|
||||
/** @var mixed Is null for remove operations */
|
||||
public $new;
|
||||
|
||||
public function __construct(int $type, $old, $new) {
|
||||
$this->type = $type;
|
||||
$this->old = $old;
|
||||
$this->new = $new;
|
||||
}
|
||||
}
|
164
lib/PhpParser/Internal/Differ.php
Normal file
164
lib/PhpParser/Internal/Differ.php
Normal file
@ -0,0 +1,164 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Internal;
|
||||
|
||||
/**
|
||||
* Implements the Myers diff algorithm.
|
||||
*
|
||||
* Myers, Eugene W. "An O (ND) difference algorithm and its variations."
|
||||
* Algorithmica 1.1 (1986): 251-266.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Differ
|
||||
{
|
||||
private $isEqual;
|
||||
|
||||
/**
|
||||
* Create differ over the given equality relation.
|
||||
*
|
||||
* @param callable $isEqual Equality relation with signature function($a, $b) : bool
|
||||
*/
|
||||
public function __construct(callable $isEqual) {
|
||||
$this->isEqual = $isEqual;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate diff (edit script) from $old to $new.
|
||||
*
|
||||
* @param array $old Original array
|
||||
* @param array $new New array
|
||||
*
|
||||
* @return DiffElem[] Diff (edit script)
|
||||
*/
|
||||
public function diff(array $old, array $new) {
|
||||
list($trace, $x, $y) = $this->calculateTrace($old, $new);
|
||||
return $this->extractDiff($trace, $x, $y, $old, $new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate diff, including "replace" operations.
|
||||
*
|
||||
* If a sequence of remove operations is followed by the same number of add operations, these
|
||||
* will be coalesced into replace operations.
|
||||
*
|
||||
* @param array $old Original array
|
||||
* @param array $new New array
|
||||
*
|
||||
* @return DiffElem[] Diff (edit script), including replace operations
|
||||
*/
|
||||
public function diffWithReplacements(array $old, array $new) {
|
||||
return $this->coalesceReplacements($this->diff($old, $new));
|
||||
}
|
||||
|
||||
private function calculateTrace(array $a, array $b) {
|
||||
$n = \count($a);
|
||||
$m = \count($b);
|
||||
$max = $n + $m;
|
||||
$v = [1 => 0];
|
||||
$trace = [];
|
||||
for ($d = 0; $d <= $max; $d++) {
|
||||
$trace[] = $v;
|
||||
for ($k = -$d; $k <= $d; $k += 2) {
|
||||
if ($k === -$d || ($k !== $d && $v[$k-1] < $v[$k+1])) {
|
||||
$x = $v[$k+1];
|
||||
} else {
|
||||
$x = $v[$k-1] + 1;
|
||||
}
|
||||
|
||||
$y = $x - $k;
|
||||
while ($x < $n && $y < $m && ($this->isEqual)($a[$x], $b[$y])) {
|
||||
$x++;
|
||||
$y++;
|
||||
}
|
||||
|
||||
$v[$k] = $x;
|
||||
if ($x >= $n && $y >= $m) {
|
||||
return [$trace, $x, $y];
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new \Exception('Should not happen');
|
||||
}
|
||||
|
||||
private function extractDiff(array $trace, int $x, int $y, array $a, array $b) {
|
||||
$result = [];
|
||||
for ($d = \count($trace) - 1; $d >= 0; $d--) {
|
||||
$v = $trace[$d];
|
||||
$k = $x - $y;
|
||||
|
||||
if ($k === -$d || ($k !== $d && $v[$k-1] < $v[$k+1])) {
|
||||
$prevK = $k + 1;
|
||||
} else {
|
||||
$prevK = $k - 1;
|
||||
}
|
||||
|
||||
$prevX = $v[$prevK];
|
||||
$prevY = $prevX - $prevK;
|
||||
|
||||
while ($x > $prevX && $y > $prevY) {
|
||||
$result[] = new DiffElem(DiffElem::TYPE_KEEP, $a[$x-1], $b[$y-1]);
|
||||
$x--;
|
||||
$y--;
|
||||
}
|
||||
|
||||
if ($d === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
while ($x > $prevX) {
|
||||
$result[] = new DiffElem(DiffElem::TYPE_REMOVE, $a[$x-1], null);
|
||||
$x--;
|
||||
}
|
||||
|
||||
while ($y > $prevY) {
|
||||
$result[] = new DiffElem(DiffElem::TYPE_ADD, null, $b[$y-1]);
|
||||
$y--;
|
||||
}
|
||||
}
|
||||
return array_reverse($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Coalesce equal-length sequences of remove+add into a replace operation.
|
||||
*
|
||||
* @param DiffElem[] $diff
|
||||
* @return DiffElem[]
|
||||
*/
|
||||
private function coalesceReplacements(array $diff) {
|
||||
$newDiff = [];
|
||||
$c = \count($diff);
|
||||
for ($i = 0; $i < $c; $i++) {
|
||||
$diffType = $diff[$i]->type;
|
||||
if ($diffType !== DiffElem::TYPE_REMOVE) {
|
||||
$newDiff[] = $diff[$i];
|
||||
continue;
|
||||
}
|
||||
|
||||
$j = $i;
|
||||
while ($j < $c && $diff[$j]->type === DiffElem::TYPE_REMOVE) {
|
||||
$j++;
|
||||
}
|
||||
|
||||
$k = $j;
|
||||
while ($k < $c && $diff[$k]->type === DiffElem::TYPE_ADD) {
|
||||
$k++;
|
||||
}
|
||||
|
||||
if ($j - $i === $k - $j) {
|
||||
$len = $j - $i;
|
||||
for ($n = 0; $n < $len; $n++) {
|
||||
$newDiff[] = new DiffElem(
|
||||
DiffElem::TYPE_REPLACE, $diff[$i + $n]->old, $diff[$j + $n]->new
|
||||
);
|
||||
}
|
||||
} else {
|
||||
for (; $i < $k; $i++) {
|
||||
$newDiff[] = $diff[$i];
|
||||
}
|
||||
}
|
||||
$i = $k - 1;
|
||||
}
|
||||
return $newDiff;
|
||||
}
|
||||
}
|
56
lib/PhpParser/Internal/PrintableNewAnonClassNode.php
Normal file
56
lib/PhpParser/Internal/PrintableNewAnonClassNode.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Internal;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
|
||||
/**
|
||||
* This node is used internally by the format-preserving pretty printer to print anonymous classes.
|
||||
*
|
||||
* The normal anonymous class structure violates assumptions about the order of token offsets.
|
||||
* Namely, the constructor arguments are part of the Expr\New_ node and follow the class node, even
|
||||
* though they are actually interleaved with them. This special node type is used temporarily to
|
||||
* restore a sane token offset order.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class PrintableNewAnonClassNode extends Expr
|
||||
{
|
||||
/** @var Node\Arg[] Arguments */
|
||||
public $args;
|
||||
/** @var null|Node\Name Name of extended class */
|
||||
public $extends;
|
||||
/** @var Node\Name[] Names of implemented interfaces */
|
||||
public $implements;
|
||||
/** @var Node\Stmt[] Statements */
|
||||
public $stmts;
|
||||
|
||||
public function __construct(
|
||||
array $args, Node\Name $extends = null, array $implements, array $stmts, array $attributes
|
||||
) {
|
||||
parent::__construct($attributes);
|
||||
$this->args = $args;
|
||||
$this->extends = $extends;
|
||||
$this->implements = $implements;
|
||||
$this->stmts = $stmts;
|
||||
}
|
||||
|
||||
public static function fromNewNode(Expr\New_ $newNode) {
|
||||
$class = $newNode->class;
|
||||
assert($class instanceof Node\Stmt\Class_);
|
||||
assert($class->name === null);
|
||||
return new self(
|
||||
$newNode->args, $class->extends, $class->implements,
|
||||
$class->stmts, $newNode->getAttributes()
|
||||
);
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_PrintableNewAnonClass';
|
||||
}
|
||||
|
||||
public function getSubNodeNames() : array {
|
||||
return ['args', 'extends', 'implements', 'stmts'];
|
||||
}
|
||||
}
|
256
lib/PhpParser/Internal/TokenStream.php
Normal file
256
lib/PhpParser/Internal/TokenStream.php
Normal file
@ -0,0 +1,256 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Internal;
|
||||
|
||||
/**
|
||||
* Provides operations on token streams, for use by pretty printer.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class TokenStream
|
||||
{
|
||||
/** @var array Tokens (in token_get_all format) */
|
||||
private $tokens;
|
||||
/** @var int[] Map from position to indentation */
|
||||
private $indentMap;
|
||||
|
||||
/**
|
||||
* Create token stream instance.
|
||||
*
|
||||
* @param array $tokens Tokens in token_get_all() format
|
||||
*/
|
||||
public function __construct(array $tokens) {
|
||||
$this->tokens = $tokens;
|
||||
$this->indentMap = $this->calcIndentMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given position is immediately surrounded by parenthesis.
|
||||
*
|
||||
* @param int $startPos Start position
|
||||
* @param int $endPos End position
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function haveParens(int $startPos, int $endPos) : bool {
|
||||
return $this->haveTokenImmediativelyBefore($startPos, '(')
|
||||
&& $this->haveTokenImmediatelyAfter($endPos, ')');
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given position is immediately surrounded by braces.
|
||||
*
|
||||
* @param int $startPos Start position
|
||||
* @param int $endPos End position
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function haveBraces(int $startPos, int $endPos) : bool {
|
||||
return $this->haveTokenImmediativelyBefore($startPos, '{')
|
||||
&& $this->haveTokenImmediatelyAfter($endPos, '}');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the position is directly preceded by a certain token type.
|
||||
*
|
||||
* During this check whitespace and comments are skipped.
|
||||
*
|
||||
* @param int $pos Position before which the token should occur
|
||||
* @param int|string $expectedTokenType Token to check for
|
||||
*
|
||||
* @return bool Whether the expected token was found
|
||||
*/
|
||||
public function haveTokenImmediativelyBefore(int $pos, $expectedTokenType) : bool {
|
||||
$tokens = $this->tokens;
|
||||
$pos--;
|
||||
for (; $pos >= 0; $pos--) {
|
||||
$tokenType = $tokens[$pos][0];
|
||||
if ($tokenType === $expectedTokenType) {
|
||||
return true;
|
||||
}
|
||||
if ($tokenType !== \T_WHITESPACE
|
||||
&& $tokenType !== \T_COMMENT && $tokenType !== \T_DOC_COMMENT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the position is directly followed by a certain token type.
|
||||
*
|
||||
* During this check whitespace and comments are skipped.
|
||||
*
|
||||
* @param int $pos Position after which the token should occur
|
||||
* @param int|string $expectedTokenType Token to check for
|
||||
*
|
||||
* @return bool Whether the expected token was found
|
||||
*/
|
||||
public function haveTokenImmediatelyAfter(int $pos, $expectedTokenType) : bool {
|
||||
$tokens = $this->tokens;
|
||||
$pos++;
|
||||
for (; $pos < \count($tokens); $pos++) {
|
||||
$tokenType = $tokens[$pos][0];
|
||||
if ($tokenType === $expectedTokenType) {
|
||||
return true;
|
||||
}
|
||||
if ($tokenType !== \T_WHITESPACE
|
||||
&& $tokenType !== \T_COMMENT && $tokenType !== \T_DOC_COMMENT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function skipLeft(int $pos, $skipTokenType) {
|
||||
$tokens = $this->tokens;
|
||||
|
||||
$pos = $this->skipLeftWhitespace($pos);
|
||||
if ($skipTokenType === \T_WHITESPACE) {
|
||||
return $pos;
|
||||
}
|
||||
|
||||
if ($tokens[$pos][0] !== $skipTokenType) {
|
||||
// Shouldn't happen. The skip token MUST be there
|
||||
throw new \Exception('Encountered unexpected token');
|
||||
}
|
||||
$pos--;
|
||||
|
||||
return $this->skipLeftWhitespace($pos);
|
||||
}
|
||||
|
||||
public function skipRight(int $pos, $skipTokenType) {
|
||||
$tokens = $this->tokens;
|
||||
|
||||
$pos = $this->skipRightWhitespace($pos);
|
||||
if ($skipTokenType === \T_WHITESPACE) {
|
||||
return $pos;
|
||||
}
|
||||
|
||||
if ($tokens[$pos][0] !== $skipTokenType) {
|
||||
// Shouldn't happen. The skip token MUST be there
|
||||
throw new \Exception('Encountered unexpected token');
|
||||
}
|
||||
$pos++;
|
||||
|
||||
return $this->skipRightWhitespace($pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return first non-whitespace token position smaller or equal to passed position.
|
||||
*
|
||||
* @param int $pos Token position
|
||||
* @return int Non-whitespace token position
|
||||
*/
|
||||
public function skipLeftWhitespace(int $pos) {
|
||||
$tokens = $this->tokens;
|
||||
for (; $pos >= 0; $pos--) {
|
||||
$type = $tokens[$pos][0];
|
||||
if ($type !== \T_WHITESPACE && $type !== \T_COMMENT && $type !== \T_DOC_COMMENT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return first non-whitespace position greater or equal to passed position.
|
||||
*
|
||||
* @param int $pos Token position
|
||||
* @return int Non-whitespace token position
|
||||
*/
|
||||
public function skipRightWhitespace(int $pos) {
|
||||
$tokens = $this->tokens;
|
||||
for ($count = \count($tokens); $pos < $count; $pos++) {
|
||||
$type = $tokens[$pos][0];
|
||||
if ($type !== \T_WHITESPACE && $type !== \T_COMMENT && $type !== \T_DOC_COMMENT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $pos;
|
||||
}
|
||||
|
||||
public function findRight($pos, $findTokenType) {
|
||||
$tokens = $this->tokens;
|
||||
for ($count = \count($tokens); $pos < $count; $pos++) {
|
||||
$type = $tokens[$pos][0];
|
||||
if ($type === $findTokenType) {
|
||||
return $pos;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get indentation before token position.
|
||||
*
|
||||
* @param int $pos Token position
|
||||
*
|
||||
* @return int Indentation depth (in spaces)
|
||||
*/
|
||||
public function getIndentationBefore(int $pos) : int {
|
||||
return $this->indentMap[$pos];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the code corresponding to a token offset range, optionally adjusted for indentation.
|
||||
*
|
||||
* @param int $from Token start position (inclusive)
|
||||
* @param int $to Token end position (exclusive)
|
||||
* @param int $indent By how much the code should be indented (can be negative as well)
|
||||
*
|
||||
* @return string Code corresponding to token range, adjusted for indentation
|
||||
*/
|
||||
public function getTokenCode(int $from, int $to, int $indent) : string {
|
||||
$tokens = $this->tokens;
|
||||
$result = '';
|
||||
for ($pos = $from; $pos < $to; $pos++) {
|
||||
$token = $tokens[$pos];
|
||||
if (\is_array($token)) {
|
||||
$type = $token[0];
|
||||
$content = $token[1];
|
||||
if ($type === \T_CONSTANT_ENCAPSED_STRING || $type === \T_ENCAPSED_AND_WHITESPACE) {
|
||||
$result .= $content;
|
||||
} else {
|
||||
// TODO Handle non-space indentation
|
||||
if ($indent < 0) {
|
||||
$result .= str_replace("\n" . str_repeat(" ", -$indent), "\n", $content);
|
||||
} elseif ($indent > 0) {
|
||||
$result .= str_replace("\n", "\n" . str_repeat(" ", $indent), $content);
|
||||
} else {
|
||||
$result .= $content;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$result .= $token;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Precalculate the indentation at every token position.
|
||||
*
|
||||
* @return int[] Token position to indentation map
|
||||
*/
|
||||
private function calcIndentMap() {
|
||||
$indentMap = [];
|
||||
$indent = 0;
|
||||
foreach ($this->tokens as $token) {
|
||||
$indentMap[] = $indent;
|
||||
|
||||
if ($token[0] === \T_WHITESPACE) {
|
||||
$content = $token[1];
|
||||
$newlinePos = \strrpos($content, "\n");
|
||||
if (false !== $newlinePos) {
|
||||
$indent = \strlen($content) - $newlinePos - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a sentinel for one past end of the file
|
||||
$indentMap[] = $indent;
|
||||
|
||||
return $indentMap;
|
||||
}
|
||||
}
|
101
lib/PhpParser/JsonDecoder.php
Normal file
101
lib/PhpParser/JsonDecoder.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
class JsonDecoder
|
||||
{
|
||||
/** @var \ReflectionClass[] Node type to reflection class map */
|
||||
private $reflectionClassCache;
|
||||
|
||||
public function decode(string $json) {
|
||||
$value = json_decode($json, true);
|
||||
if (json_last_error()) {
|
||||
throw new \RuntimeException('JSON decoding error: ' . json_last_error_msg());
|
||||
}
|
||||
|
||||
return $this->decodeRecursive($value);
|
||||
}
|
||||
|
||||
private function decodeRecursive($value) {
|
||||
if (\is_array($value)) {
|
||||
if (isset($value['nodeType'])) {
|
||||
if ($value['nodeType'] === 'Comment' || $value['nodeType'] === 'Comment_Doc') {
|
||||
return $this->decodeComment($value);
|
||||
}
|
||||
return $this->decodeNode($value);
|
||||
}
|
||||
return $this->decodeArray($value);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function decodeArray(array $array) : array {
|
||||
$decodedArray = [];
|
||||
foreach ($array as $key => $value) {
|
||||
$decodedArray[$key] = $this->decodeRecursive($value);
|
||||
}
|
||||
return $decodedArray;
|
||||
}
|
||||
|
||||
private function decodeNode(array $value) : Node {
|
||||
$nodeType = $value['nodeType'];
|
||||
if (!\is_string($nodeType)) {
|
||||
throw new \RuntimeException('Node type must be a string');
|
||||
}
|
||||
|
||||
$reflectionClass = $this->reflectionClassFromNodeType($nodeType);
|
||||
/** @var Node $node */
|
||||
$node = $reflectionClass->newInstanceWithoutConstructor();
|
||||
|
||||
if (isset($value['attributes'])) {
|
||||
if (!\is_array($value['attributes'])) {
|
||||
throw new \RuntimeException('Attributes must be an array');
|
||||
}
|
||||
|
||||
$node->setAttributes($this->decodeArray($value['attributes']));
|
||||
}
|
||||
|
||||
foreach ($value as $name => $subNode) {
|
||||
if ($name === 'nodeType' || $name === 'attributes') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$node->$name = $this->decodeRecursive($subNode);
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
private function decodeComment(array $value) : Comment {
|
||||
$className = $value['nodeType'] === 'Comment' ? Comment::class : Comment\Doc::class;
|
||||
if (!isset($value['text'])) {
|
||||
throw new \RuntimeException('Comment must have text');
|
||||
}
|
||||
|
||||
return new $className(
|
||||
$value['text'], $value['line'] ?? -1, $value['filePos'] ?? -1, $value['tokenPos'] ?? -1
|
||||
);
|
||||
}
|
||||
|
||||
private function reflectionClassFromNodeType(string $nodeType) : \ReflectionClass {
|
||||
if (!isset($this->reflectionClassCache[$nodeType])) {
|
||||
$className = $this->classNameFromNodeType($nodeType);
|
||||
$this->reflectionClassCache[$nodeType] = new \ReflectionClass($className);
|
||||
}
|
||||
return $this->reflectionClassCache[$nodeType];
|
||||
}
|
||||
|
||||
private function classNameFromNodeType(string $nodeType) : string {
|
||||
$className = 'PhpParser\\Node\\' . strtr($nodeType, '_', '\\');
|
||||
if (class_exists($className)) {
|
||||
return $className;
|
||||
}
|
||||
|
||||
$className .= '_';
|
||||
if (class_exists($className)) {
|
||||
return $className;
|
||||
}
|
||||
|
||||
throw new \RuntimeException("Unknown node type \"$nodeType\"");
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
@ -27,21 +27,21 @@ class Lexer
|
||||
* 'endTokenPos', 'startFilePos', 'endFilePos'. The option defaults to the
|
||||
* first three. For more info see getNextToken() docs.
|
||||
*/
|
||||
public function __construct(array $options = array()) {
|
||||
public function __construct(array $options = []) {
|
||||
// map from internal tokens to PhpParser tokens
|
||||
$this->tokenMap = $this->createTokenMap();
|
||||
|
||||
// map of tokens to drop while lexing (the map is only used for isset lookup,
|
||||
// that's why the value is simply set to 1; the value is never actually used.)
|
||||
$this->dropTokens = array_fill_keys(
|
||||
array(T_WHITESPACE, T_OPEN_TAG, T_COMMENT, T_DOC_COMMENT), 1
|
||||
[\T_WHITESPACE, \T_OPEN_TAG, \T_COMMENT, \T_DOC_COMMENT], 1
|
||||
);
|
||||
|
||||
// the usedAttributes member is a map of the used attribute names to a dummy
|
||||
// value (here "true")
|
||||
$options += array(
|
||||
'usedAttributes' => array('comments', 'startLine', 'endLine'),
|
||||
);
|
||||
$options += [
|
||||
'usedAttributes' => ['comments', 'startLine', 'endLine'],
|
||||
];
|
||||
$this->usedAttributes = array_fill_keys($options['usedAttributes'], true);
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@ class Lexer
|
||||
* @param ErrorHandler|null $errorHandler Error handler to use for lexing errors. Defaults to
|
||||
* ErrorHandler\Throwing
|
||||
*/
|
||||
public function startLexing($code, ErrorHandler $errorHandler = null) {
|
||||
public function startLexing(string $code, ErrorHandler $errorHandler = null) {
|
||||
if (null === $errorHandler) {
|
||||
$errorHandler = new ErrorHandler\Throwing();
|
||||
}
|
||||
@ -71,7 +71,7 @@ class Lexer
|
||||
|
||||
$scream = ini_set('xdebug.scream', '0');
|
||||
|
||||
$this->resetErrors();
|
||||
error_clear_last();
|
||||
$this->tokens = @token_get_all($code);
|
||||
$this->handleErrors($errorHandler);
|
||||
|
||||
@ -80,17 +80,6 @@ class Lexer
|
||||
}
|
||||
}
|
||||
|
||||
protected function resetErrors() {
|
||||
if (function_exists('error_clear_last')) {
|
||||
error_clear_last();
|
||||
} else {
|
||||
// set error_get_last() to defined state by forcing an undefined variable error
|
||||
set_error_handler(function() { return false; }, 0);
|
||||
@$undefinedVariable;
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
||||
|
||||
private function handleInvalidCharacterRange($start, $end, $line, ErrorHandler $errorHandler) {
|
||||
for ($i = $start; $i < $end; $i++) {
|
||||
$chr = $this->code[$i];
|
||||
@ -117,22 +106,30 @@ class Lexer
|
||||
}
|
||||
}
|
||||
|
||||
private function isUnterminatedComment($token) {
|
||||
return ($token[0] === T_COMMENT || $token[0] === T_DOC_COMMENT)
|
||||
/**
|
||||
* Check whether comment token is unterminated.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isUnterminatedComment($token) : bool {
|
||||
return ($token[0] === \T_COMMENT || $token[0] === \T_DOC_COMMENT)
|
||||
&& substr($token[1], 0, 2) === '/*'
|
||||
&& substr($token[1], -2) !== '*/';
|
||||
}
|
||||
|
||||
private function errorMayHaveOccurred() {
|
||||
/**
|
||||
* Check whether an error *may* have occurred during tokenization.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function errorMayHaveOccurred() : bool {
|
||||
if (defined('HHVM_VERSION')) {
|
||||
// In HHVM token_get_all() does not throw warnings, so we need to conservatively
|
||||
// assume that an error occurred
|
||||
return true;
|
||||
}
|
||||
|
||||
$error = error_get_last();
|
||||
return null !== $error
|
||||
&& false === strpos($error['message'], 'Undefined variable');
|
||||
return null !== error_get_last();
|
||||
}
|
||||
|
||||
protected function handleErrors(ErrorHandler $errorHandler) {
|
||||
@ -147,7 +144,7 @@ class Lexer
|
||||
|
||||
$filePos = 0;
|
||||
$line = 1;
|
||||
foreach ($this->tokens as $i => $token) {
|
||||
foreach ($this->tokens as $token) {
|
||||
$tokenValue = \is_string($token) ? $token : $token[1];
|
||||
$tokenLen = \strlen($tokenValue);
|
||||
|
||||
@ -156,7 +153,7 @@ class Lexer
|
||||
$nextFilePos = strpos($this->code, $tokenValue, $filePos);
|
||||
$this->handleInvalidCharacterRange(
|
||||
$filePos, $nextFilePos, $line, $errorHandler);
|
||||
$filePos = $nextFilePos;
|
||||
$filePos = (int) $nextFilePos;
|
||||
}
|
||||
|
||||
$filePos += $tokenLen;
|
||||
@ -176,7 +173,7 @@ class Lexer
|
||||
|
||||
// Emulate the PHP behavior
|
||||
$isDocComment = isset($comment[3]) && $comment[3] === '*';
|
||||
$this->tokens[] = [$isDocComment ? T_DOC_COMMENT : T_COMMENT, $comment, $line];
|
||||
$this->tokens[] = [$isDocComment ? \T_DOC_COMMENT : \T_COMMENT, $comment, $line];
|
||||
} else {
|
||||
// Invalid characters at the end of the input
|
||||
$this->handleInvalidCharacterRange(
|
||||
@ -185,15 +182,17 @@ class Lexer
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for unterminated comment
|
||||
$lastToken = $this->tokens[count($this->tokens) - 1];
|
||||
if ($this->isUnterminatedComment($lastToken)) {
|
||||
$errorHandler->handleError(new Error('Unterminated comment', [
|
||||
'startLine' => $line - substr_count($lastToken[1], "\n"),
|
||||
'endLine' => $line,
|
||||
'startFilePos' => $filePos - \strlen($lastToken[1]),
|
||||
'endFilePos' => $filePos,
|
||||
]));
|
||||
if (count($this->tokens) > 0) {
|
||||
// Check for unterminated comment
|
||||
$lastToken = $this->tokens[count($this->tokens) - 1];
|
||||
if ($this->isUnterminatedComment($lastToken)) {
|
||||
$errorHandler->handleError(new Error('Unterminated comment', [
|
||||
'startLine' => $line - substr_count($lastToken[1], "\n"),
|
||||
'endLine' => $line,
|
||||
'startFilePos' => $filePos - \strlen($lastToken[1]),
|
||||
'endFilePos' => $filePos,
|
||||
]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -219,9 +218,9 @@ class Lexer
|
||||
*
|
||||
* @return int Token id
|
||||
*/
|
||||
public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) {
|
||||
$startAttributes = array();
|
||||
$endAttributes = array();
|
||||
public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) : int {
|
||||
$startAttributes = [];
|
||||
$endAttributes = [];
|
||||
|
||||
while (1) {
|
||||
if (isset($this->tokens[++$this->pos])) {
|
||||
@ -254,20 +253,20 @@ class Lexer
|
||||
} elseif (!isset($this->dropTokens[$token[0]])) {
|
||||
$value = $token[1];
|
||||
$id = $this->tokenMap[$token[0]];
|
||||
if (T_CLOSE_TAG === $token[0]) {
|
||||
if (\T_CLOSE_TAG === $token[0]) {
|
||||
$this->prevCloseTagHasNewline = false !== strpos($token[1], "\n");
|
||||
} else if (T_INLINE_HTML === $token[0]) {
|
||||
} elseif (\T_INLINE_HTML === $token[0]) {
|
||||
$startAttributes['hasLeadingNewline'] = $this->prevCloseTagHasNewline;
|
||||
}
|
||||
|
||||
$this->line += substr_count($value, "\n");
|
||||
$this->filePos += \strlen($value);
|
||||
} else {
|
||||
if (T_COMMENT === $token[0] || T_DOC_COMMENT === $token[0]) {
|
||||
if (\T_COMMENT === $token[0] || \T_DOC_COMMENT === $token[0]) {
|
||||
if (isset($this->usedAttributes['comments'])) {
|
||||
$comment = T_DOC_COMMENT === $token[0]
|
||||
? new Comment\Doc($token[1], $this->line, $this->filePos)
|
||||
: new Comment($token[1], $this->line, $this->filePos);
|
||||
$comment = \T_DOC_COMMENT === $token[0]
|
||||
? new Comment\Doc($token[1], $this->line, $this->filePos, $this->pos)
|
||||
: new Comment($token[1], $this->line, $this->filePos, $this->pos);
|
||||
$startAttributes['comments'][] = $comment;
|
||||
}
|
||||
}
|
||||
@ -303,7 +302,7 @@ class Lexer
|
||||
*
|
||||
* @return array Array of tokens in token_get_all() format
|
||||
*/
|
||||
public function getTokens() {
|
||||
public function getTokens() : array {
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
@ -312,7 +311,7 @@ class Lexer
|
||||
*
|
||||
* @return string Remaining text
|
||||
*/
|
||||
public function handleHaltCompiler() {
|
||||
public function handleHaltCompiler() : string {
|
||||
// text after T_HALT_COMPILER, still including ();
|
||||
$textAfter = substr($this->code, $this->filePos);
|
||||
|
||||
@ -327,7 +326,7 @@ class Lexer
|
||||
$this->pos = count($this->tokens);
|
||||
|
||||
// return with (); removed
|
||||
return (string) substr($textAfter, strlen($matches[0])); // (string) converts false to ''
|
||||
return substr($textAfter, strlen($matches[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -339,26 +338,26 @@ class Lexer
|
||||
*
|
||||
* @return array The token map
|
||||
*/
|
||||
protected function createTokenMap() {
|
||||
$tokenMap = array();
|
||||
protected function createTokenMap() : array {
|
||||
$tokenMap = [];
|
||||
|
||||
// 256 is the minimum possible token number, as everything below
|
||||
// it is an ASCII value
|
||||
for ($i = 256; $i < 1000; ++$i) {
|
||||
if (T_DOUBLE_COLON === $i) {
|
||||
if (\T_DOUBLE_COLON === $i) {
|
||||
// T_DOUBLE_COLON is equivalent to T_PAAMAYIM_NEKUDOTAYIM
|
||||
$tokenMap[$i] = Tokens::T_PAAMAYIM_NEKUDOTAYIM;
|
||||
} elseif(T_OPEN_TAG_WITH_ECHO === $i) {
|
||||
} elseif(\T_OPEN_TAG_WITH_ECHO === $i) {
|
||||
// T_OPEN_TAG_WITH_ECHO with dropped T_OPEN_TAG results in T_ECHO
|
||||
$tokenMap[$i] = Tokens::T_ECHO;
|
||||
} elseif(T_CLOSE_TAG === $i) {
|
||||
} elseif(\T_CLOSE_TAG === $i) {
|
||||
// T_CLOSE_TAG is equivalent to ';'
|
||||
$tokenMap[$i] = ord(';');
|
||||
} elseif ('UNKNOWN' !== $name = token_name($i)) {
|
||||
if ('T_HASHBANG' === $name) {
|
||||
// HHVM uses a special token for #! hashbang lines
|
||||
$tokenMap[$i] = Tokens::T_INLINE_HTML;
|
||||
} else if (defined($name = 'PhpParser\Parser\Tokens::' . $name)) {
|
||||
} elseif (defined($name = Tokens::class . '::' . $name)) {
|
||||
// Other tokens can be mapped directly
|
||||
$tokenMap[$i] = constant($name);
|
||||
}
|
||||
@ -367,11 +366,11 @@ class Lexer
|
||||
|
||||
// HHVM uses a special token for numbers that overflow to double
|
||||
if (defined('T_ONUMBER')) {
|
||||
$tokenMap[T_ONUMBER] = Tokens::T_DNUMBER;
|
||||
$tokenMap[\T_ONUMBER] = Tokens::T_DNUMBER;
|
||||
}
|
||||
// HHVM also has a separate token for the __COMPILER_HALT_OFFSET__ constant
|
||||
if (defined('T_COMPILER_HALT_OFFSET')) {
|
||||
$tokenMap[T_COMPILER_HALT_OFFSET] = Tokens::T_STRING;
|
||||
$tokenMap[\T_COMPILER_HALT_OFFSET] = Tokens::T_STRING;
|
||||
}
|
||||
|
||||
return $tokenMap;
|
||||
|
@ -1,174 +1,8 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Lexer;
|
||||
|
||||
use PhpParser\ErrorHandler;
|
||||
use PhpParser\Parser\Tokens;
|
||||
|
||||
class Emulative extends \PhpParser\Lexer
|
||||
{
|
||||
protected $newKeywords;
|
||||
protected $inObjectAccess;
|
||||
|
||||
const T_ELLIPSIS = 1001;
|
||||
const T_POW = 1002;
|
||||
const T_POW_EQUAL = 1003;
|
||||
const T_COALESCE = 1004;
|
||||
const T_SPACESHIP = 1005;
|
||||
const T_YIELD_FROM = 1006;
|
||||
|
||||
const PHP_7_0 = '7.0.0dev';
|
||||
const PHP_5_6 = '5.6.0rc1';
|
||||
|
||||
public function __construct(array $options = array()) {
|
||||
parent::__construct($options);
|
||||
|
||||
$newKeywordsPerVersion = array(
|
||||
// No new keywords since PHP 5.5
|
||||
);
|
||||
|
||||
$this->newKeywords = array();
|
||||
foreach ($newKeywordsPerVersion as $version => $newKeywords) {
|
||||
if (version_compare(PHP_VERSION, $version, '>=')) {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->newKeywords += $newKeywords;
|
||||
}
|
||||
|
||||
if (version_compare(PHP_VERSION, self::PHP_7_0, '>=')) {
|
||||
return;
|
||||
}
|
||||
$this->tokenMap[self::T_COALESCE] = Tokens::T_COALESCE;
|
||||
$this->tokenMap[self::T_SPACESHIP] = Tokens::T_SPACESHIP;
|
||||
$this->tokenMap[self::T_YIELD_FROM] = Tokens::T_YIELD_FROM;
|
||||
|
||||
if (version_compare(PHP_VERSION, self::PHP_5_6, '>=')) {
|
||||
return;
|
||||
}
|
||||
$this->tokenMap[self::T_ELLIPSIS] = Tokens::T_ELLIPSIS;
|
||||
$this->tokenMap[self::T_POW] = Tokens::T_POW;
|
||||
$this->tokenMap[self::T_POW_EQUAL] = Tokens::T_POW_EQUAL;
|
||||
}
|
||||
|
||||
public function startLexing($code, ErrorHandler $errorHandler = null) {
|
||||
$this->inObjectAccess = false;
|
||||
|
||||
parent::startLexing($code, $errorHandler);
|
||||
if ($this->requiresEmulation($code)) {
|
||||
$this->emulateTokens();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if the code is potentially using features that require emulation.
|
||||
*/
|
||||
protected function requiresEmulation($code) {
|
||||
if (version_compare(PHP_VERSION, self::PHP_7_0, '>=')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (preg_match('(\?\?|<=>|yield[ \n\r\t]+from)', $code)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (version_compare(PHP_VERSION, self::PHP_5_6, '>=')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return preg_match('(\.\.\.|(?<!/)\*\*(?!/))', $code);
|
||||
}
|
||||
|
||||
/*
|
||||
* Emulates tokens for newer PHP versions.
|
||||
*/
|
||||
protected function emulateTokens() {
|
||||
// We need to manually iterate and manage a count because we'll change
|
||||
// the tokens array on the way
|
||||
$line = 1;
|
||||
for ($i = 0, $c = count($this->tokens); $i < $c; ++$i) {
|
||||
$replace = null;
|
||||
if (isset($this->tokens[$i + 1])) {
|
||||
if ($this->tokens[$i] === '?' && $this->tokens[$i + 1] === '?') {
|
||||
array_splice($this->tokens, $i, 2, array(
|
||||
array(self::T_COALESCE, '??', $line)
|
||||
));
|
||||
$c--;
|
||||
continue;
|
||||
}
|
||||
if ($this->tokens[$i][0] === T_IS_SMALLER_OR_EQUAL
|
||||
&& $this->tokens[$i + 1] === '>'
|
||||
) {
|
||||
array_splice($this->tokens, $i, 2, array(
|
||||
array(self::T_SPACESHIP, '<=>', $line)
|
||||
));
|
||||
$c--;
|
||||
continue;
|
||||
}
|
||||
if ($this->tokens[$i] === '*' && $this->tokens[$i + 1] === '*') {
|
||||
array_splice($this->tokens, $i, 2, array(
|
||||
array(self::T_POW, '**', $line)
|
||||
));
|
||||
$c--;
|
||||
continue;
|
||||
}
|
||||
if ($this->tokens[$i] === '*' && $this->tokens[$i + 1][0] === T_MUL_EQUAL) {
|
||||
array_splice($this->tokens, $i, 2, array(
|
||||
array(self::T_POW_EQUAL, '**=', $line)
|
||||
));
|
||||
$c--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->tokens[$i + 2])) {
|
||||
if ($this->tokens[$i][0] === T_YIELD && $this->tokens[$i + 1][0] === T_WHITESPACE
|
||||
&& $this->tokens[$i + 2][0] === T_STRING
|
||||
&& !strcasecmp($this->tokens[$i + 2][1], 'from')
|
||||
) {
|
||||
array_splice($this->tokens, $i, 3, array(
|
||||
array(
|
||||
self::T_YIELD_FROM,
|
||||
$this->tokens[$i][1] . $this->tokens[$i + 1][1] . $this->tokens[$i + 2][1],
|
||||
$line
|
||||
)
|
||||
));
|
||||
$c -= 2;
|
||||
$line += substr_count($this->tokens[$i][1], "\n");
|
||||
continue;
|
||||
}
|
||||
if ($this->tokens[$i] === '.' && $this->tokens[$i + 1] === '.'
|
||||
&& $this->tokens[$i + 2] === '.'
|
||||
) {
|
||||
array_splice($this->tokens, $i, 3, array(
|
||||
array(self::T_ELLIPSIS, '...', $line)
|
||||
));
|
||||
$c -= 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (\is_array($this->tokens[$i])) {
|
||||
$line += substr_count($this->tokens[$i][1], "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) {
|
||||
$token = parent::getNextToken($value, $startAttributes, $endAttributes);
|
||||
|
||||
// replace new keywords by their respective tokens. This is not done
|
||||
// if we currently are in an object access (e.g. in $obj->namespace
|
||||
// "namespace" stays a T_STRING tokens and isn't converted to T_NAMESPACE)
|
||||
if (Tokens::T_STRING === $token && !$this->inObjectAccess) {
|
||||
if (isset($this->newKeywords[strtolower($value)])) {
|
||||
return $this->newKeywords[strtolower($value)];
|
||||
}
|
||||
} else {
|
||||
// keep track of whether we currently are in an object access (after ->)
|
||||
$this->inObjectAccess = Tokens::T_OBJECT_OPERATOR === $token;
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
/* No features requiring emulation have been added in PHP > 7.0 */
|
||||
}
|
||||
|
285
lib/PhpParser/NameContext.php
Normal file
285
lib/PhpParser/NameContext.php
Normal file
@ -0,0 +1,285 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class NameContext
|
||||
{
|
||||
/** @var null|Name Current namespace */
|
||||
protected $namespace;
|
||||
|
||||
/** @var Name[][] Map of format [aliasType => [aliasName => originalName]] */
|
||||
protected $aliases = [];
|
||||
|
||||
/** @var Name[][] Same as $aliases but preserving original case */
|
||||
protected $origAliases = [];
|
||||
|
||||
/** @var ErrorHandler Error handler */
|
||||
protected $errorHandler;
|
||||
|
||||
/**
|
||||
* Create a name context.
|
||||
*
|
||||
* @param ErrorHandler $errorHandler Error handling used to report errors
|
||||
*/
|
||||
public function __construct(ErrorHandler $errorHandler) {
|
||||
$this->errorHandler = $errorHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new namespace.
|
||||
*
|
||||
* This also resets the alias table.
|
||||
*
|
||||
* @param Name|null $namespace Null is the global namespace
|
||||
*/
|
||||
public function startNamespace(Name $namespace = null) {
|
||||
$this->namespace = $namespace;
|
||||
$this->origAliases = $this->aliases = [
|
||||
Stmt\Use_::TYPE_NORMAL => [],
|
||||
Stmt\Use_::TYPE_FUNCTION => [],
|
||||
Stmt\Use_::TYPE_CONSTANT => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an alias / import.
|
||||
*
|
||||
* @param Name $name Original name
|
||||
* @param string $aliasName Aliased name
|
||||
* @param int $type One of Stmt\Use_::TYPE_*
|
||||
* @param array $errorAttrs Attributes to use to report an error
|
||||
*/
|
||||
public function addAlias(Name $name, string $aliasName, int $type, array $errorAttrs = []) {
|
||||
// Constant names are case sensitive, everything else case insensitive
|
||||
if ($type === Stmt\Use_::TYPE_CONSTANT) {
|
||||
$aliasLookupName = $aliasName;
|
||||
} else {
|
||||
$aliasLookupName = strtolower($aliasName);
|
||||
}
|
||||
|
||||
if (isset($this->aliases[$type][$aliasLookupName])) {
|
||||
$typeStringMap = [
|
||||
Stmt\Use_::TYPE_NORMAL => '',
|
||||
Stmt\Use_::TYPE_FUNCTION => 'function ',
|
||||
Stmt\Use_::TYPE_CONSTANT => 'const ',
|
||||
];
|
||||
|
||||
$this->errorHandler->handleError(new Error(
|
||||
sprintf(
|
||||
'Cannot use %s%s as %s because the name is already in use',
|
||||
$typeStringMap[$type], $name, $aliasName
|
||||
),
|
||||
$errorAttrs
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
$this->aliases[$type][$aliasLookupName] = $name;
|
||||
$this->origAliases[$type][$aliasName] = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current namespace.
|
||||
*
|
||||
* @return null|Name Namespace (or null if global namespace)
|
||||
*/
|
||||
public function getNamespace() {
|
||||
return $this->namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get resolved name.
|
||||
*
|
||||
* @param Name $name Name to resolve
|
||||
* @param int $type One of Stmt\Use_::TYPE_{FUNCTION|CONSTANT}
|
||||
*
|
||||
* @return null|Name Resolved name, or null if static resolution is not possible
|
||||
*/
|
||||
public function getResolvedName(Name $name, int $type) {
|
||||
// don't resolve special class names
|
||||
if ($type === Stmt\Use_::TYPE_NORMAL && $name->isSpecialClassName()) {
|
||||
if (!$name->isUnqualified()) {
|
||||
$this->errorHandler->handleError(new Error(
|
||||
sprintf("'\\%s' is an invalid class name", $name->toString()),
|
||||
$name->getAttributes()
|
||||
));
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
// fully qualified names are already resolved
|
||||
if ($name->isFullyQualified()) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
// Try to resolve aliases
|
||||
if (null !== $resolvedName = $this->resolveAlias($name, $type)) {
|
||||
return $resolvedName;
|
||||
}
|
||||
|
||||
if ($type !== Stmt\Use_::TYPE_NORMAL && $name->isUnqualified()) {
|
||||
if (null === $this->namespace) {
|
||||
// outside of a namespace unaliased unqualified is same as fully qualified
|
||||
return new FullyQualified($name, $name->getAttributes());
|
||||
}
|
||||
|
||||
// Cannot resolve statically
|
||||
return null;
|
||||
}
|
||||
|
||||
// if no alias exists prepend current namespace
|
||||
return FullyQualified::concat($this->namespace, $name, $name->getAttributes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get resolved class name.
|
||||
*
|
||||
* @param Name $name Class ame to resolve
|
||||
*
|
||||
* @return Name Resolved name
|
||||
*/
|
||||
public function getResolvedClassName(Name $name) : Name {
|
||||
return $this->getResolvedName($name, Stmt\Use_::TYPE_NORMAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get possible ways of writing a fully qualified name (e.g., by making use of aliases).
|
||||
*
|
||||
* @param string $name Fully-qualified name (without leading namespace separator)
|
||||
* @param int $type One of Stmt\Use_::TYPE_*
|
||||
*
|
||||
* @return Name[] Possible representations of the name
|
||||
*/
|
||||
public function getPossibleNames(string $name, int $type) : array {
|
||||
$lcName = strtolower($name);
|
||||
|
||||
if ($type === Stmt\Use_::TYPE_NORMAL) {
|
||||
// self, parent and static must always be unqualified
|
||||
if ($lcName === "self" || $lcName === "parent" || $lcName === "static") {
|
||||
return [new Name($name)];
|
||||
}
|
||||
}
|
||||
|
||||
// Collect possible ways to write this name, starting with the fully-qualified name
|
||||
$possibleNames = [new FullyQualified($name)];
|
||||
|
||||
if (null !== $nsRelativeName = $this->getNamespaceRelativeName($name, $lcName, $type)) {
|
||||
// Make sure there is no alias that makes the normally namespace-relative name
|
||||
// into something else
|
||||
if (null === $this->resolveAlias($nsRelativeName, $type)) {
|
||||
$possibleNames[] = $nsRelativeName;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for relevant namespace use statements
|
||||
foreach ($this->origAliases[Stmt\Use_::TYPE_NORMAL] as $alias => $orig) {
|
||||
$lcOrig = $orig->toLowerString();
|
||||
if (0 === strpos($lcName, $lcOrig . '\\')) {
|
||||
$possibleNames[] = new Name($alias . substr($name, strlen($lcOrig)));
|
||||
}
|
||||
}
|
||||
|
||||
// Check for relevant type-specific use statements
|
||||
foreach ($this->origAliases[$type] as $alias => $orig) {
|
||||
if ($type === Stmt\Use_::TYPE_CONSTANT) {
|
||||
// Constants are are complicated-sensitive
|
||||
$normalizedOrig = $this->normalizeConstName($orig->toString());
|
||||
if ($normalizedOrig === $this->normalizeConstName($name)) {
|
||||
$possibleNames[] = new Name($alias);
|
||||
}
|
||||
} else {
|
||||
// Everything else is case-insensitive
|
||||
if ($orig->toLowerString() === $lcName) {
|
||||
$possibleNames[] = new Name($alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $possibleNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shortest representation of this fully-qualified name.
|
||||
*
|
||||
* @param string $name Fully-qualified name (without leading namespace separator)
|
||||
* @param int $type One of Stmt\Use_::TYPE_*
|
||||
*
|
||||
* @return Name Shortest representation
|
||||
*/
|
||||
public function getShortName(string $name, int $type) : Name {
|
||||
$possibleNames = $this->getPossibleNames($name, $type);
|
||||
|
||||
// Find shortest name
|
||||
$shortestName = null;
|
||||
$shortestLength = \INF;
|
||||
foreach ($possibleNames as $possibleName) {
|
||||
$length = strlen($possibleName->toCodeString());
|
||||
if ($length < $shortestLength) {
|
||||
$shortestName = $possibleName;
|
||||
$shortestLength = $length;
|
||||
}
|
||||
}
|
||||
|
||||
return $shortestName;
|
||||
}
|
||||
|
||||
private function resolveAlias(Name $name, $type) {
|
||||
$firstPart = $name->getFirst();
|
||||
|
||||
if ($name->isQualified()) {
|
||||
// resolve aliases for qualified names, always against class alias table
|
||||
$checkName = strtolower($firstPart);
|
||||
if (isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$checkName])) {
|
||||
$alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$checkName];
|
||||
return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes());
|
||||
}
|
||||
} elseif ($name->isUnqualified()) {
|
||||
// constant aliases are case-sensitive, function aliases case-insensitive
|
||||
$checkName = $type === Stmt\Use_::TYPE_CONSTANT ? $firstPart : strtolower($firstPart);
|
||||
if (isset($this->aliases[$type][$checkName])) {
|
||||
// resolve unqualified aliases
|
||||
return new FullyQualified($this->aliases[$type][$checkName], $name->getAttributes());
|
||||
}
|
||||
}
|
||||
|
||||
// No applicable aliases
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getNamespaceRelativeName(string $name, string $lcName, int $type) {
|
||||
if (null === $this->namespace) {
|
||||
return new Name($name);
|
||||
}
|
||||
|
||||
if ($type === Stmt\Use_::TYPE_CONSTANT) {
|
||||
// The constants true/false/null always resolve to the global symbols, even inside a
|
||||
// namespace, so they may be used without qualification
|
||||
if ($lcName === "true" || $lcName === "false" || $lcName === "null") {
|
||||
return new Name($name);
|
||||
}
|
||||
}
|
||||
|
||||
$namespacePrefix = strtolower($this->namespace . '\\');
|
||||
if (0 === strpos($lcName, $namespacePrefix)) {
|
||||
return new Name(substr($name, strlen($namespacePrefix)));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function normalizeConstName(string $name) {
|
||||
$nsSep = strrpos($name, '\\');
|
||||
if (false === $nsSep) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
// Constants have case-insensitive namespace and case-sensitive short-name
|
||||
$ns = substr($name, 0, $nsSep);
|
||||
$shortName = substr($name, $nsSep + 1);
|
||||
return strtolower($ns) . '\\' . $shortName;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
@ -9,28 +9,88 @@ interface Node
|
||||
*
|
||||
* @return string Type of the node
|
||||
*/
|
||||
public function getType();
|
||||
public function getType() : string;
|
||||
|
||||
/**
|
||||
* Gets the names of the sub nodes.
|
||||
*
|
||||
* @return array Names of sub nodes
|
||||
*/
|
||||
public function getSubNodeNames();
|
||||
public function getSubNodeNames() : array;
|
||||
|
||||
/**
|
||||
* Gets line the node started in (alias of getStartLine).
|
||||
*
|
||||
* @return int Start line (or -1 if not available)
|
||||
*/
|
||||
public function getLine() : int;
|
||||
|
||||
/**
|
||||
* Gets line the node started in.
|
||||
*
|
||||
* @return int Line
|
||||
* Requires the 'startLine' attribute to be enabled in the lexer (enabled by default).
|
||||
*
|
||||
* @return int Start line (or -1 if not available)
|
||||
*/
|
||||
public function getLine();
|
||||
public function getStartLine() : int;
|
||||
|
||||
/**
|
||||
* Sets line the node started in.
|
||||
* Gets the line the node ended in.
|
||||
*
|
||||
* @param int $line Line
|
||||
* Requires the 'endLine' attribute to be enabled in the lexer (enabled by default).
|
||||
*
|
||||
* @return int End line (or -1 if not available)
|
||||
*/
|
||||
public function setLine($line);
|
||||
public function getEndLine() : int;
|
||||
|
||||
/**
|
||||
* Gets the token offset of the first token that is part of this node.
|
||||
*
|
||||
* The offset is an index into the array returned by Lexer::getTokens().
|
||||
*
|
||||
* Requires the 'startTokenPos' attribute to be enabled in the lexer (DISABLED by default).
|
||||
*
|
||||
* @return int Token start position (or -1 if not available)
|
||||
*/
|
||||
public function getStartTokenPos() : int;
|
||||
|
||||
/**
|
||||
* Gets the token offset of the last token that is part of this node.
|
||||
*
|
||||
* The offset is an index into the array returned by Lexer::getTokens().
|
||||
*
|
||||
* Requires the 'endTokenPos' attribute to be enabled in the lexer (DISABLED by default).
|
||||
*
|
||||
* @return int Token end position (or -1 if not available)
|
||||
*/
|
||||
public function getEndTokenPos() : int;
|
||||
|
||||
/**
|
||||
* Gets the file offset of the first character that is part of this node.
|
||||
*
|
||||
* Requires the 'startFilePos' attribute to be enabled in the lexer (DISABLED by default).
|
||||
*
|
||||
* @return int File start position (or -1 if not available)
|
||||
*/
|
||||
public function getStartFilePos() : int;
|
||||
|
||||
/**
|
||||
* Gets the file offset of the last character that is part of this node.
|
||||
*
|
||||
* Requires the 'endFilePos' attribute to be enabled in the lexer (DISABLED by default).
|
||||
*
|
||||
* @return int File end position (or -1 if not available)
|
||||
*/
|
||||
public function getEndFilePos() : int;
|
||||
|
||||
/**
|
||||
* Gets all comments directly preceding this node.
|
||||
*
|
||||
* The comments are also available through the "comments" attribute.
|
||||
*
|
||||
* @return Comment[]
|
||||
*/
|
||||
public function getComments() : array;
|
||||
|
||||
/**
|
||||
* Gets the doc comment of the node.
|
||||
@ -56,7 +116,7 @@ interface Node
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function setAttribute($key, $value);
|
||||
public function setAttribute(string $key, $value);
|
||||
|
||||
/**
|
||||
* Returns whether an attribute exists.
|
||||
@ -65,7 +125,7 @@ interface Node
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasAttribute($key);
|
||||
public function hasAttribute(string $key) : bool;
|
||||
|
||||
/**
|
||||
* Returns the value of an attribute.
|
||||
@ -75,12 +135,19 @@ interface Node
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function &getAttribute($key, $default = null);
|
||||
public function getAttribute(string $key, $default = null);
|
||||
|
||||
/**
|
||||
* Returns all attributes for the given node.
|
||||
* Returns all the attributes of this node.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAttributes();
|
||||
}
|
||||
public function getAttributes() : array;
|
||||
|
||||
/**
|
||||
* Replaces all the attributes of this node.
|
||||
*
|
||||
* @param array $attributes
|
||||
*/
|
||||
public function setAttributes(array $attributes);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node;
|
||||
|
||||
@ -21,14 +21,18 @@ class Arg extends NodeAbstract
|
||||
* @param bool $unpack Whether to unpack the argument
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $value, $byRef = false, $unpack = false, array $attributes = array()) {
|
||||
public function __construct(Expr $value, bool $byRef = false, bool $unpack = false, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->value = $value;
|
||||
$this->byRef = $byRef;
|
||||
$this->unpack = $unpack;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
return array('value', 'byRef', 'unpack');
|
||||
public function getSubNodeNames() : array {
|
||||
return ['value', 'byRef', 'unpack'];
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Arg';
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,15 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
/**
|
||||
* @property Name $namespacedName Namespaced name (for class constants, if using NameResolver)
|
||||
*/
|
||||
class Const_ extends NodeAbstract
|
||||
{
|
||||
/** @var string Name */
|
||||
/** @var Identifier Name */
|
||||
public $name;
|
||||
/** @var Expr Value */
|
||||
public $value;
|
||||
@ -14,17 +17,21 @@ class Const_ extends NodeAbstract
|
||||
/**
|
||||
* Constructs a const node for use in class const and const statements.
|
||||
*
|
||||
* @param string $name Name
|
||||
* @param Expr $value Value
|
||||
* @param array $attributes Additional attributes
|
||||
* @param string|Identifier $name Name
|
||||
* @param Expr $value Value
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($name, Expr $value, array $attributes = array()) {
|
||||
public function __construct($name, Expr $value, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->name = $name;
|
||||
$this->name = \is_string($name) ? new Identifier($name) : $name;
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
return array('name', 'value');
|
||||
public function getSubNodeNames() : array {
|
||||
return ['name', 'value'];
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Const';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node;
|
||||
|
||||
@ -6,4 +6,4 @@ use PhpParser\NodeAbstract;
|
||||
|
||||
abstract class Expr extends NodeAbstract
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
@ -18,13 +18,17 @@ class ArrayDimFetch extends Expr
|
||||
* @param null|Expr $dim Array index / dim
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $var, Expr $dim = null, array $attributes = array()) {
|
||||
public function __construct(Expr $var, Expr $dim = null, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->var = $var;
|
||||
$this->dim = $dim;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
return array('var', 'dim');
|
||||
public function getSubNodeNames() : array {
|
||||
return ['var', 'dim'];
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_ArrayDimFetch';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
@ -21,14 +21,18 @@ class ArrayItem extends Expr
|
||||
* @param bool $byRef Whether to assign by reference
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $value, Expr $key = null, $byRef = false, array $attributes = array()) {
|
||||
public function __construct(Expr $value, Expr $key = null, bool $byRef = false, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->key = $key;
|
||||
$this->value = $value;
|
||||
$this->byRef = $byRef;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
return array('key', 'value', 'byRef');
|
||||
public function getSubNodeNames() : array {
|
||||
return ['key', 'value', 'byRef'];
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_ArrayItem';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
@ -19,12 +19,16 @@ class Array_ extends Expr
|
||||
* @param ArrayItem[] $items Items of the array
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(array $items = array(), array $attributes = array()) {
|
||||
public function __construct(array $items = [], array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->items = $items;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
return array('items');
|
||||
public function getSubNodeNames() : array {
|
||||
return ['items'];
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_Array';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
@ -18,13 +18,17 @@ class Assign extends Expr
|
||||
* @param Expr $expr Expression
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $var, Expr $expr, array $attributes = array()) {
|
||||
public function __construct(Expr $var, Expr $expr, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->var = $var;
|
||||
$this->expr = $expr;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
return array('var', 'expr');
|
||||
public function getSubNodeNames() : array {
|
||||
return ['var', 'expr'];
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_Assign';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
@ -18,13 +18,13 @@ abstract class AssignOp extends Expr
|
||||
* @param Expr $expr Expression
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $var, Expr $expr, array $attributes = array()) {
|
||||
public function __construct(Expr $var, Expr $expr, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->var = $var;
|
||||
$this->expr = $expr;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
return array('var', 'expr');
|
||||
public function getSubNodeNames() : array {
|
||||
return ['var', 'expr'];
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
@ -6,4 +6,7 @@ use PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
class BitwiseAnd extends AssignOp
|
||||
{
|
||||
}
|
||||
public function getType() : string {
|
||||
return 'Expr_AssignOp_BitwiseAnd';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
@ -6,4 +6,7 @@ use PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
class BitwiseOr extends AssignOp
|
||||
{
|
||||
}
|
||||
public function getType() : string {
|
||||
return 'Expr_AssignOp_BitwiseOr';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
@ -6,4 +6,7 @@ use PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
class BitwiseXor extends AssignOp
|
||||
{
|
||||
}
|
||||
public function getType() : string {
|
||||
return 'Expr_AssignOp_BitwiseXor';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
@ -6,4 +6,7 @@ use PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
class Concat extends AssignOp
|
||||
{
|
||||
}
|
||||
public function getType() : string {
|
||||
return 'Expr_AssignOp_Concat';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
@ -6,4 +6,7 @@ use PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
class Div extends AssignOp
|
||||
{
|
||||
}
|
||||
public function getType() : string {
|
||||
return 'Expr_AssignOp_Div';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
@ -6,4 +6,7 @@ use PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
class Minus extends AssignOp
|
||||
{
|
||||
}
|
||||
public function getType() : string {
|
||||
return 'Expr_AssignOp_Minus';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
@ -6,4 +6,7 @@ use PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
class Mod extends AssignOp
|
||||
{
|
||||
}
|
||||
public function getType() : string {
|
||||
return 'Expr_AssignOp_Mod';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
@ -6,4 +6,7 @@ use PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
class Mul extends AssignOp
|
||||
{
|
||||
}
|
||||
public function getType() : string {
|
||||
return 'Expr_AssignOp_Mul';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
@ -6,4 +6,7 @@ use PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
class Plus extends AssignOp
|
||||
{
|
||||
}
|
||||
public function getType() : string {
|
||||
return 'Expr_AssignOp_Plus';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
@ -6,4 +6,7 @@ use PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
class Pow extends AssignOp
|
||||
{
|
||||
}
|
||||
public function getType() : string {
|
||||
return 'Expr_AssignOp_Pow';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
@ -6,4 +6,7 @@ use PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
class ShiftLeft extends AssignOp
|
||||
{
|
||||
}
|
||||
public function getType() : string {
|
||||
return 'Expr_AssignOp_ShiftLeft';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
@ -6,4 +6,7 @@ use PhpParser\Node\Expr\AssignOp;
|
||||
|
||||
class ShiftRight extends AssignOp
|
||||
{
|
||||
}
|
||||
public function getType() : string {
|
||||
return 'Expr_AssignOp_ShiftRight';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
@ -18,13 +18,17 @@ class AssignRef extends Expr
|
||||
* @param Expr $expr Expression
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $var, Expr $expr, array $attributes = array()) {
|
||||
public function __construct(Expr $var, Expr $expr, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->var = $var;
|
||||
$this->expr = $expr;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
return array('var', 'expr');
|
||||
public function getSubNodeNames() : array {
|
||||
return ['var', 'expr'];
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_AssignRef';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
@ -18,13 +18,23 @@ abstract class BinaryOp extends Expr
|
||||
* @param Expr $right The right hand side expression
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $left, Expr $right, array $attributes = array()) {
|
||||
public function __construct(Expr $left, Expr $right, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->left = $left;
|
||||
$this->right = $right;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
return array('left', 'right');
|
||||
public function getSubNodeNames() : array {
|
||||
return ['left', 'right'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the operator sigil for this binary operation.
|
||||
*
|
||||
* In the case there are multiple possible sigils for an operator, this method does not
|
||||
* necessarily return the one used in the parsed code.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getOperatorSigil() : string;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
@ -6,4 +6,11 @@ use PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
class BitwiseAnd extends BinaryOp
|
||||
{
|
||||
}
|
||||
public function getOperatorSigil() : string {
|
||||
return '&';
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_BinaryOp_BitwiseAnd';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
@ -6,4 +6,11 @@ use PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
class BitwiseOr extends BinaryOp
|
||||
{
|
||||
}
|
||||
public function getOperatorSigil() : string {
|
||||
return '|';
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_BinaryOp_BitwiseOr';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
@ -6,4 +6,11 @@ use PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
class BitwiseXor extends BinaryOp
|
||||
{
|
||||
}
|
||||
public function getOperatorSigil() : string {
|
||||
return '^';
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_BinaryOp_BitwiseXor';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
@ -6,4 +6,11 @@ use PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
class BooleanAnd extends BinaryOp
|
||||
{
|
||||
}
|
||||
public function getOperatorSigil() : string {
|
||||
return '&&';
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_BinaryOp_BooleanAnd';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
@ -6,4 +6,11 @@ use PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
class BooleanOr extends BinaryOp
|
||||
{
|
||||
}
|
||||
public function getOperatorSigil() : string {
|
||||
return '||';
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_BinaryOp_BooleanOr';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
@ -6,4 +6,11 @@ use PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
class Coalesce extends BinaryOp
|
||||
{
|
||||
public function getOperatorSigil() : string {
|
||||
return '??';
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_BinaryOp_Coalesce';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
@ -6,4 +6,11 @@ use PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
class Concat extends BinaryOp
|
||||
{
|
||||
}
|
||||
public function getOperatorSigil() : string {
|
||||
return '.';
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_BinaryOp_Concat';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
@ -6,4 +6,11 @@ use PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
class Div extends BinaryOp
|
||||
{
|
||||
}
|
||||
public function getOperatorSigil() : string {
|
||||
return '/';
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_BinaryOp_Div';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
@ -6,4 +6,11 @@ use PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
class Equal extends BinaryOp
|
||||
{
|
||||
}
|
||||
public function getOperatorSigil() : string {
|
||||
return '==';
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_BinaryOp_Equal';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
@ -6,4 +6,11 @@ use PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
class Greater extends BinaryOp
|
||||
{
|
||||
}
|
||||
public function getOperatorSigil() : string {
|
||||
return '>';
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_BinaryOp_Greater';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
@ -6,4 +6,11 @@ use PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
class GreaterOrEqual extends BinaryOp
|
||||
{
|
||||
}
|
||||
public function getOperatorSigil() : string {
|
||||
return '>=';
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_BinaryOp_GreaterOrEqual';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
@ -6,4 +6,11 @@ use PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
class Identical extends BinaryOp
|
||||
{
|
||||
}
|
||||
public function getOperatorSigil() : string {
|
||||
return '===';
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_BinaryOp_Identical';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
@ -6,4 +6,11 @@ use PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
class LogicalAnd extends BinaryOp
|
||||
{
|
||||
}
|
||||
public function getOperatorSigil() : string {
|
||||
return 'and';
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_BinaryOp_LogicalAnd';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
@ -6,4 +6,11 @@ use PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
class LogicalOr extends BinaryOp
|
||||
{
|
||||
}
|
||||
public function getOperatorSigil() : string {
|
||||
return 'or';
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_BinaryOp_LogicalOr';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
@ -6,4 +6,11 @@ use PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
class LogicalXor extends BinaryOp
|
||||
{
|
||||
}
|
||||
public function getOperatorSigil() : string {
|
||||
return 'xor';
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_BinaryOp_LogicalXor';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
@ -6,4 +6,11 @@ use PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
class Minus extends BinaryOp
|
||||
{
|
||||
}
|
||||
public function getOperatorSigil() : string {
|
||||
return '-';
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_BinaryOp_Minus';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
@ -6,4 +6,11 @@ use PhpParser\Node\Expr\BinaryOp;
|
||||
|
||||
class Mod extends BinaryOp
|
||||
{
|
||||
}
|
||||
public function getOperatorSigil() : string {
|
||||
return '%';
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_BinaryOp_Mod';
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user