Compare commits

...

300 Commits

Author SHA1 Message Date
e4a54fa90a Release PHP-Parser 4.0.1 2018-03-25 19:35:16 +02:00
3125b54e5e Link to composer/xdebug-handler 2018-03-25 19:34:45 +02:00
7f8ff1b9a4 Add support for list reference assignments
RFC: https://wiki.php.net/rfc/list_reference_assignment
2018-03-10 16:48:20 +01:00
91a462ce76 PHP 7.3: Add support for trailing commas in calls
RFC: https://wiki.php.net/rfc/trailing-comma-function-calls
2018-03-10 16:41:20 +01:00
610617fe93 Also add new() builder 2018-03-03 22:25:58 +01:00
ff2d85dc6b Add constFetch() and classConstFetch() builders 2018-03-03 22:14:42 +01:00
b998d1e9b5 Add funcCall(), methodCall() and staticCall() builders 2018-03-03 15:40:51 +01:00
6aba7624ed Add replacement sanity check in traverser 2018-03-03 13:22:36 +01:00
9c18e3db49 Release PHP-Parser 4.0.0 2018-02-28 21:39:30 +01:00
ae52aadb43 Fix merge leftovers in changelog 2018-02-28 21:37:11 +01:00
9cea94000a Merge branch '3.x'
Conflicts:
	CHANGELOG.md
	lib/PhpParser/NodeAbstract.php
2018-02-28 21:32:04 +01:00
bb87e28e7d Release PHP-Parser 3.1.5 2018-02-28 21:30:58 +01:00
7484acb88b Enable syntax highlighting for one code block 2018-02-28 21:27:29 +01:00
bc5ba47b28 Add links to component docs 2018-02-28 21:14:04 +01:00
b9996315a6 Add more extensive docs for node visitors
Also document NodeFinder.
2018-02-28 21:00:42 +01:00
de3470190c Proofreading the docs - very minor changes! 2018-02-28 18:15:02 +01:00
1c3eabb000 Replace string by class call (#477)
For better PHP-Scoper compatibility
2018-02-23 22:44:40 +01:00
8d1e86b47f Move node dumper docs into basic usage
Also mentioned php-parse here.
2018-02-08 17:17:32 +01:00
ff10cc9d08 Move JSON representation into component documentation
Also add -j flag to php-parse script to get a JSON dump. Also
drop obsolete flag for XML dumping.
2018-02-08 16:36:04 +01:00
14454326e9 Fix misspellings 2018-02-06 14:47:39 +01:00
3a45c1a121 Support PHPUnit 7 2018-02-02 11:52:56 +01:00
ad9c42b66a Merge branch '3.x'
Conflicts:
	lib/PhpParser/Parser/Php5.php
	lib/PhpParser/Parser/Php7.php
2018-01-30 23:07:11 +01:00
08215e7646 Fix duplicate switch comment assignment
Fixes #469.
2018-01-30 23:06:20 +01:00
c18bb27723 Release PHP-Parser 4.0 Beta 1 2018-01-27 19:06:36 +01:00
dd0adcc96c Move code gen docs to components and improve
Mention non-fluent helper methods.
2018-01-27 18:56:21 +01:00
e4505de346 Move FAQ into component documentation 2018-01-27 18:40:22 +01:00
a513ccabb7 Improve constant evaluation and add docs
Split into evaluateDirectly() and evaluateSilently(), to be able
to treat errors more gracefully. Add documentation for constant
evaluation.
2018-01-27 17:47:45 +01:00
d817818b5d Move TokenStream into Internal namespace 2018-01-27 13:40:20 +01:00
6a273c9fbd Remove Autoloader class 2018-01-25 23:13:53 +01:00
c2d3ecad35 Merge branch '3.x'
Conflicts:
	CHANGELOG.md
2018-01-25 22:32:25 +01:00
e57b3a0978 Release PHP-Parser 3.1.4 2018-01-25 22:31:33 +01:00
1cdb280a30 Merge branch '3.x' 2018-01-25 22:28:08 +01:00
d01fafcb40 Handle +(++$x) and -(--$x) as well 2018-01-25 22:27:37 +01:00
67df02c844 Update license year 2018-01-25 22:23:06 +01:00
b85b6b3519 Merge branch '3.x'
Conflicts:
	lib/PhpParser/PrettyPrinter/Standard.php
2018-01-25 22:18:32 +01:00
94c715d97e Fix pretty printing of -(-$x) and +(+$x)
Fixes #459.
2018-01-25 22:17:35 +01:00
4dacbb8d39 FPPP: Fix indentation on list insertion
Use indentation of last list element, instead of indentation
before the insertion point.

Fixes #466.
2018-01-25 22:08:40 +01:00
aa685e711a Bump to PHP 7.2 in documentation 2018-01-14 22:10:37 +01:00
edafeb85c4 [CS] Order uses A -> Z 2018-01-13 16:08:27 +01:00
68d07c4662 [CS] New line in the end of file 2018-01-13 16:08:26 +01:00
8fae99aafe [CS] Remove spaces 2018-01-13 16:08:25 +01:00
c7ada124d0 [CS] Use ::class notation instead of string
Conflicts:
	test/PhpParser/ParserFactoryTest.php
	test/PhpParser/ParserTest.php
2018-01-13 16:08:17 +01:00
f6617e6d25 [CS] Space after ternary operator 2018-01-13 16:04:00 +01:00
2499534729 [CS] Whitespace before return type 2018-01-13 16:03:59 +01:00
e0a2043089 [CS] Space before casting 2018-01-13 16:03:57 +01:00
e6e52abae7 [CS] Trim whitespaces 2018-01-13 16:03:56 +01:00
7f72c84122 [CS] Open class brackets in new line 2018-01-13 16:03:55 +01:00
fc8ac71e76 [CS] Trim whitespaces inside arrays 2018-01-13 16:03:54 +01:00
a8968caa5b [CS] Remove extra lines 2018-01-13 16:03:53 +01:00
5285df8f22 [CS] Use elseif instead of else if
Conflicts:
	lib/PhpParser/TokenStream.php
2018-01-13 16:03:36 +01:00
4366aa2fb0 [CS] Use __DIR__ instead of dirname(__FILE__) 2018-01-13 16:02:14 +01:00
bf7d811cda Add methods visibility (#464)
* [PSR-2] Visibility before static

* [PSR-2] Declare method Visibility
2018-01-10 19:07:41 +01:00
248b29ecf6 Add public visibility to getType method (#463) 2018-01-10 18:57:48 +01:00
bcb45d31eb Trailing whitespaces (#461)
Signed-off-by: Gabriel Caruso <carusogabriel34@gmail.com>
2018-01-08 11:10:03 +01:00
3bc31488ce Combine issets (#460) 2018-01-04 13:36:01 +01:00
92b39e3d1f Fix nNextFreeElement for arrays with trailing comma
Ugh.
2017-12-26 21:17:36 +01:00
1c7fd314d1 FPPP: Add heuristic for multi-line lists 2017-12-26 21:14:36 +01:00
fb8175567e Simplify delayed add code
$insertStr always stays the same, so no reason to store it
separately.
2017-12-26 17:53:36 +01:00
63abf9cb3f Release PHP-Parser 4.0 Alpha 3 2017-12-26 17:29:20 +01:00
ceb4932ca8 Clean merge leftover in changelog (#456) 2017-12-26 17:25:14 +01:00
aa289c9694 Merge branch '3.x'
Conflicts:
	CHANGELOG.md
2017-12-26 15:44:25 +01:00
579f4ce846 Release PHP-Parser 3.1.3 2017-12-26 15:43:21 +01:00
8d3cb5f57b FPPP: Fallback if we must insert a block 2017-12-26 15:41:38 +01:00
de82a40d04 FPPP: Add broken InlineHTML test cases 2017-12-26 15:08:30 +01:00
a86151f24f FPPP: Fix fallback for inline HTML 2017-12-26 15:08:29 +01:00
e6e8791848 Missing # in method description 2017-12-25 14:28:33 +01:00
83b958763f Refactoring tests 2017-12-15 21:42:34 +01:00
4dbb02c57b Use Null Coalesce Operator 2017-12-15 21:41:41 +01:00
4fcdac40d1 FPPP: Fix insertion of multiple elems at start 2017-12-02 15:10:15 +01:00
04feb90d79 Rename abc1.test to basic.test
And split off fixup.test.
2017-12-01 23:15:50 +01:00
dc3ace55c3 FPPP: Support insert at start of list node (#446) 2017-12-01 22:09:51 +01:00
e5453f0d46 Extract pSingleQuotedString as an extension point
And add some more string formatting tests.
2017-12-01 18:31:21 +01:00
b507fa43da Ensure names are not empty 2017-12-01 18:13:55 +01:00
336a49b428 FPPP: Fix comment indentation (#443) 2017-11-13 13:27:27 +01:00
fa174b093f Merge branch '3.x' 2017-11-13 01:15:38 +01:00
94ca9a7ab9 Use Tokens::class in lexer
Ref #441.
2017-11-13 01:14:55 +01:00
0bb74e03aa Drop two more getType() usages in builders 2017-11-13 00:24:01 +01:00
8d3f48ab75 Switch some auxiliary pretty printer tables away from getType()
Instead use class names via ::class.
2017-11-13 00:18:44 +01:00
1c11626f0a Add explicit getType() methods
Rather than automatically deriving getType() from the class name.
2017-11-12 21:27:14 +01:00
05e2cd287e Merge branch '3.x'
Conflicts:
	lib/PhpParser/NodeAbstract.php
2017-11-12 21:12:27 +01:00
bac91b426e Correctly determine Type of Node when PHP-Parser's namespaces are prefixed
Hi there,

I'm working on mutation testing framework ([Infection](https://github.com/infection/infection/)) that is distributed as a PHAR. One of this goal is to run target project's test suite against mutated code. Since we use reflection and load project's autoloader, we want to avoid potential conflicts between vendor files of Infection itself and the target project.

To avoid this issue, there is a project calld [PHP-Scoper](https://github.com/humbug/php-scoper). What it does is it prefixes all the namespaces of the library (including vendor folder) with some character(s), for example namespace `Infection\Mutator\PublicVisibility` is transformed to `ScoperAbc123\Infection\Mutant\PublicVisibility`.

But since it also prefixes vendor folder, PHP-Parser's classes are prefixed as well and `NodeAbstract::getType()` after this prefixing works incorrectly.

There is a hardcoded number `15` which means to remove `'PhpParser\Node'` (length=15) substring from the FQCN.

Code:

```php
// PHPParser\Node\Stmt\Declare_ -> Stmt_Declare

return strtr(substr(rtrim(get_class($this), '_'), 15), '\\', '_');
```

What I suggest is a little be more dynamic solution, to correctly extract class name (type) from the ***prefixed*** FQCL:

`ScoperAbc123\PHPParser\Node\Stmt\Declare_` -> `Stmt_Declare`
2017-11-12 21:11:41 +01:00
a75164c77e Rename YYNLSTATES to numNonLeafStates 2017-11-12 16:09:00 +01:00
c59e75f873 Clear parser state directly after parsing
I don't think this makes any difference in practical scenarios, but
it makes memory usage analysis simpler.
2017-11-11 16:54:52 +01:00
4392a7b164 Fix PHP5 static call fixup
Variable nodes directly store the variable name as a string, they
don't use VarLikeIdentifier. So remove this wrapper if it exists.
2017-11-11 12:23:14 +01:00
a659240dc2 Release PHP-Parser 4.0.0 Alpha 2 2017-11-10 23:36:59 +01:00
68e9b91e9c Use fully-qualified names for global constant references 2017-11-10 23:33:12 +01:00
7162b36f2d Add bullet point summary to main README as well 2017-11-10 22:45:27 +01:00
73be07672b Docs: Add performance section 2017-11-10 22:44:06 +01:00
e2c0c598a3 FPPP: Optimize indentation calculation
Precalculate the indentation for every position. The previous
implementation was quadratic for long lines.
2017-11-04 22:43:53 +01:00
66f88cfa07 Update changelog 2017-11-04 18:22:06 +01:00
9b6a2577fa FPPP: Convert some checks to assertions
Don't just silently fall back if we receive illegal input.
2017-11-04 18:14:12 +01:00
457fe049a8 Ensure nodes have full complement of location info 2017-11-04 18:13:00 +01:00
6a2e1ae440 FPPP: Graceful handling of comment changes 2017-11-04 17:56:11 +01:00
47c973b3aa Store start token position in comments 2017-11-04 17:45:14 +01:00
56bc8ebb9b FPPP: Add support for removal from list nodes 2017-11-04 17:23:17 +01:00
361398bc0d Merge branch '3.x'
Conflicts:
	CHANGELOG.md
2017-11-04 12:49:58 +01:00
08131e7ff2 Release PHP-Parser 3.1.2 2017-11-04 12:48:34 +01:00
fd6e8d7ea8 Merge branch '3.x'
Conflicts:
	lib/PhpParser/Builder/Namespace_.php
	lib/PhpParser/Node/Stmt/ClassMethod.php
	test/PhpParser/Builder/NamespaceTest.php
2017-11-04 12:45:04 +01:00
0ba710affa Add setDocComment() to namespace build (#437) 2017-11-04 12:43:02 +01:00
72231abe6d ClassMethod stmts property can be null (#435)
Conflicts:
	lib/PhpParser/Node/Stmt/ClassMethod.php
2017-11-04 12:38:45 +01:00
7254040998 Test FPPP roundtrip in test_old 2017-11-04 12:37:34 +01:00
57bf378412 add missing strict_types to lib 2017-11-03 13:24:19 +01:00
b58157f024 add strict_types to tests + fix explode type 2017-11-03 13:24:19 +01:00
ab596db285 ClassMethod stmts property can be null (#435) 2017-11-01 20:26:21 +01:00
7f92edf3a1 Docs: add missing $stmts (#433) 2017-10-31 22:48:00 +01:00
837579a40c Fix spelling in documentation (#434) 2017-10-31 22:47:42 +01:00
b241a121a3 Fix trait alias to semi-reserved name parsing
This was missing the explicit construction of an Identifier node.
2017-10-29 14:15:48 +01:00
5054a68bfb Reset origTokens in resetState()
In case one pretty printer is used in both preserving and
non-preserving mode.
2017-10-29 13:28:46 +01:00
d16f050d74 Travis: Enable PHP 7.2 2017-10-29 12:35:23 +01:00
5900d78cc9 FPPP: Support anonymous classes (#432)
This is a huge hack... We temporarily create a new node with the
correct structure and use that for printing.

I think it would be better to always use a separate node type for
NewAnonClass, rather than using a combination of New and Class,
but this would require some larger changes, as this node type would
have to be both Expr and ClassLike, which is not possible right now,
as the latter is a class rather than an interface...
2017-10-29 12:26:12 +01:00
776275361a FPPP: Support modifier changes (#428)
I decided against introducing a node type for this. In the end it
would require special handling anyway.
2017-10-25 22:27:29 +02:00
651341d53b docs: "return" => "remove" typo (#430) 2017-10-22 13:50:47 +02:00
001f2d3f76 Release PHP-Parser 4.0 Alpha 1 2017-10-18 18:50:17 +02:00
b0242d31f8 Add quick start to README 2017-10-18 18:43:39 +02:00
f5de7f9894 FPPP: Check that nodes are nodes in pArray() 2017-10-18 15:42:01 +02:00
Wes
3193f7aae7 Fix documentation for "replaceNodes" option in NameResolver (#425) 2017-10-15 22:20:52 +02:00
bbec051e51 FPPP: Consolidate indentation levels
A bit annoying that we have to keep $this->nl always synchronized.
2017-10-06 18:21:08 +02:00
f071b66013 Print comments of inserted statements 2017-10-06 17:58:56 +02:00
57bc42517b Move IdentifierTest where it belongs
Ooops...
2017-10-06 15:07:35 +02:00
bb2ac91115 Move leading space out of pStmt_Else
Same as previous commit. Also add support for pStmt_If->else
insertion.
2017-10-06 14:56:59 +02:00
e3888cbe02 Move leading space out of pStmt_ElseIf
This is not part of the elseif itself and as such belongs in the
pStmt_If printer.
2017-10-06 14:52:52 +02:00
310155832a FPPP: Support insertion into list subnodes
With some rough edges...
2017-10-06 14:43:41 +02:00
ed8a744cd5 FPPP: Switch pArray() to use node list diffing 2017-10-06 12:02:00 +02:00
69aec6fb5b Add implementation of Myers differ
Intended to be used in the format-preserving pretty printer for
handling changes in node lists.
2017-10-05 21:53:45 +02:00
75880fbe2d Make code more explicit 2017-10-05 18:50:48 +02:00
65de924493 Add Identifier::toString()
For symmetry with the Name API.

Also add some missing unit tests.
2017-10-03 20:57:48 +02:00
eaee6687e0 Tweaks 2017-10-03 19:13:20 +02:00
f5f3b0d49d Add name resolution, pretty printing component docs
The docs are receiving too little love...
2017-10-03 19:09:59 +02:00
f6cc85a796 FPPP: Fall back if comment in list node changes (#420) 2017-10-02 21:18:13 +02:00
cc600b67f6 Merge branch '3.x'
Conflicts:
	lib/PhpParser/Parser/Php5.php
	lib/PhpParser/Parser/Php7.php
2017-10-01 16:55:54 +02:00
d418bf3951 Preserve comments on empty blocks (#382) 2017-10-01 16:54:43 +02:00
4b1d9667af Add constant expression evaluator (#402) 2017-09-30 18:56:44 +02:00
a02990a39a Remove PHP 5 substr() workaround
In PHP 7 it no longer returns false instead of '' in this case.
2017-09-29 17:51:44 +02:00
403a7c5315 Update changelog 2017-09-29 17:41:19 +02:00
cc328a4c9c Add get{Start,End}{Line,TokenPos,FilePos}() methods 2017-09-29 17:34:15 +02:00
3d4621bbea Don't return by ref from getAttribute()
This is not worth the few times where it might be useful.
2017-09-29 17:14:27 +02:00
d8f9173390 Add Node::getComments() method 2017-09-29 17:09:16 +02:00
df334eacaa Add back commented test
We're not testing HHVM anymore, and I've already fixed this bug
upstream.
2017-09-26 18:47:47 +02:00
5cfdc2e0a7 Merge branch '3.x'
Conflicts:
	lib/PhpParser/Parser/Php5.php
	lib/PhpParser/Parser/Php7.php
	test/PhpParser/ParserTest.php
2017-09-26 18:46:44 +02:00
5a9fbca54a Add attribute for namespace kinds (#417)
One of KIND_SEMICOLON or KIND_BRACED.
2017-09-26 18:45:05 +02:00
4f8f773b9f Remove leftover property intialization in pretty printer (#416) 2017-09-17 15:25:14 +08:00
69ed40e401 Don't continue of searching if a global namespace declaration is found 2017-09-15 21:53:21 +08:00
ec8692fb9e Fix InlineHTML indentation 2017-09-03 19:02:07 +02:00
b1cd07a7b5 Extract TokenStream class from pretty printer 2017-09-03 18:54:22 +02:00
61e624bc9d Replace noIndent mechanism in pretty printer
Instead store newline with indentation in $nl property and use it
where needed.

The implementation should be changed to compilet indentLevel and
fpIndentLevel, I don't think these need to be kept separate.
2017-09-03 18:11:25 +02:00
31065389f1 Remove workarounds for reserved keyword method names
No longer necessary in PHP 7.
2017-09-02 20:08:20 +02:00
829c6c3c71 Add initial changelog for version 4.0 2017-09-02 20:03:10 +02:00
e145102345 Merge branch '3.x' 2017-09-02 19:11:27 +02:00
a1e8e1a30e Release PHP-Parser 3.1.1 2017-09-02 19:10:46 +02:00
efd39a67a2 Merge branch '3.x'
Conflicts:
	lib/PhpParser/Builder/Trait_.php
	test/PhpParser/Builder/TraitTest.php
2017-08-29 23:20:47 +02:00
d77e6cd6e9 Allow TraitUse statements in trait builder
Fixes #413.
2017-08-29 23:18:59 +02:00
fa12dc8a22 Merge branch '3.x' 2017-08-29 23:15:04 +02:00
a10780ca0d Handle Nop statement after brace-style namespace
Fixes #412.
2017-08-29 23:14:27 +02:00
9373a8e9f5 Implement JsonDecoder
Converts JSON representation back into node tree.
2017-08-18 23:56:12 +02:00
e2e99f269b Add strict_types to lib code 2017-08-18 23:00:13 +02:00
7b36ca3b6c Add isSpecialClassName() method to Identifier and Name 2017-08-15 22:59:52 +02:00
d97cc3d96e Add toLowerString() method to Name and Identifier
Avoids patterns like strtolower((string) $name) when using
strict types.
2017-08-15 22:49:16 +02:00
6ab69a7dc9 [cs] apply short array to bin/php-parse 2017-08-15 22:25:39 +02:00
af12807451 [cs] apply same to tests 2017-08-13 21:14:28 +02:00
9d599040b7 [cs] spaces 2017-08-13 21:14:28 +02:00
05e6725b7a [cs] use strict comparison where allowed 2017-08-13 21:14:28 +02:00
ec535ea14e [cs] use PHP 5.4 short array, since PHP 7.0 is min version 2017-08-13 21:14:28 +02:00
58bf92e4f7 [cs] remove unused imports 2017-08-13 21:14:28 +02:00
a664b28248 travis: add dash to make command format united (#407)
Prevents possible error when adding a second line
2017-08-13 14:04:53 +02:00
edec3bb07a Docs: fix FAQ link anchor (#406) 2017-08-08 17:53:02 +02:00
770c314dd1 Docs: Use PHP 7 return types (#405) 2017-08-07 22:49:26 +02:00
94b5017e89 Fix missing semicolon in yacc grammar files (#403) 2017-07-30 12:23:40 +02:00
fe069d6de2 Merge branch '3.x' 2017-07-28 16:45:57 +02:00
4d4896e553 Release PHP-Parser 3.1.0 2017-07-28 16:45:09 +02:00
d11ef05aaf Merge branch '3.x' 2017-07-19 17:14:23 +02:00
6fa073879e Remove deprecation of Name::$parts
It doesn't look like this is going away for now, and we currently
don't have the APIs to cover all existing usages elegantly.
2017-07-19 17:13:10 +02:00
5a3a1ec25c Handle LC_NUMERIC with comma decimal separator
Closes #399.
2017-07-19 17:10:55 +02:00
441e6e1d31 Fixed incorrect class inheritance (#394)
Generated example was incorrect - you cannot define import alias:
use Some\Other\Thingy as SomeClass;

and then define a class with the same name:
abstract class SomeClass ...

Class names changed to avoid conflict between import alias and abstract class name.
2017-07-19 16:55:00 +02:00
8bdcb47815 Simplify ternary operator for PHP7 (#395) 2017-06-30 18:19:13 +02:00
46e7fea72d Merge branch '3.x'
Conflicts:
	test/PhpParser/Builder/ParamTest.php
2017-06-28 23:13:33 +02:00
4a7d011317 Add support for object type 2017-06-28 23:12:13 +02:00
4fea054ee0 Merge branch '3.x'
Conflicts:
	lib/PhpParser/Parser/Php7.php
2017-06-28 23:06:38 +02:00
7f862ac21c Add support for trailing comma in group use 2017-06-28 23:05:28 +02:00
0808939f81 Release PHP-Parser 3.0.6 2017-06-28 22:53:48 +02:00
19dde1363e Treat special names correctly in getShortName()
Also change the API to accept a string rather than a FullyQualified
name instance, as this is not appropriate for symbols like "self".
2017-06-13 19:51:22 +02:00
5b43809b48 Drop HHVM support
Not dropping any of the HHVM workaround code yet. If someone sorts
out the Travis build, I'm fine with keeping HHVM support.
2017-06-03 15:33:25 +02:00
947574d3dc Merge branch '3.x' 2017-06-03 15:31:50 +02:00
7bfc320bda Switch to dist: trusty, so HHVM runs 2017-06-03 15:25:50 +02:00
7646b31907 Merge branch '3.x' 2017-06-03 15:18:44 +02:00
bc0bff3f87 Fix method name in tests 2017-06-03 15:18:23 +02:00
c28b8556f5 Fix Lexer errorHandling when there is no tokens
I encounter an issue when no tokens exist. An error `Undefined offset -1` is triggered.
2017-06-03 15:17:45 +02:00
24d583d5c3 Fix example code to work (#390) 2017-05-28 12:35:04 +02:00
b2c6497d63 Remove undefined variable hack
No longer necessary, as we don't support PHP 5 anymore.
2017-05-07 19:57:24 +02:00
4e162bd0ea Fix type annotation for List::$items 2017-05-07 19:56:14 +02:00
73dc35cbbd Allow null in ClassMethod::getStmts() 2017-05-07 19:56:14 +02:00
Wes
b1af3d1f7d Add Node::setAttributes() (#385) 2017-05-05 18:18:44 +02:00
92275bdfa8 Remove Node::setLine() method 2017-04-29 13:01:02 +02:00
56d57d472a Merge branch '3.x' 2017-04-29 12:58:50 +02:00
3da86df48f Deprecate Node::setLine() 2017-04-29 12:58:35 +02:00
5ec59eebe4 Fix some typos 2017-04-28 23:17:50 +02:00
a32e3797d4 Generate PHP 7 type annotations 2017-04-28 21:40:59 +02:00
e5fbdd6bda Add upgrading notes 2017-04-28 21:23:55 +02:00
3da189769c Distinguish between implicit/explicit alias
The UseUse::$alias node can now be null if an alias is not
explicitly given. As such "use Foo\Bar" and "use Foo\Bar as Bar"
are now represented differently.

The UseUse->getAlias() method replicates the previous semantics,
by returning "Bar" in both cases.
2017-04-28 21:05:01 +02:00
a6846e3b71 Always use Identifier nodes
The parser will now always generate Identifier nodes (for
non-namespaced identifiers). This obsoletes the useIdentifierNodes
parser option.

Node constructors still accepts strings and will implicitly create
an Identifier wrapper. Identifier implement __toString(), so that
outside of strict-mode many things continue to work without changes.
2017-04-28 20:57:32 +02:00
3b4abbfc97 Add BuilderFactors->args(Add BuilderFactors->args()) 2017-04-28 18:13:06 +02:00
7f6477ed83 Combine class/non-class name resolution into single method 2017-04-28 17:18:13 +02:00
56b810e91d Add getShortName() API
PHP's name resolution rules are f'ing complicated.
2017-04-28 17:10:30 +02:00
6168abd9a0 Extract name resolution logic into NameContext
All the generic name resolution logic is now available as a separate
class with a public API.
2017-04-27 21:06:07 +02:00
888b9dcf30 Drop misspellt class constant 2017-04-27 18:28:10 +02:00
e1ab92275c Allow tests without assertions 2017-04-27 18:20:12 +02:00
3c44785e82 Bump phpunit version (#381) 2017-04-27 18:14:07 +02:00
8635365a30 Fix some typing issues 2017-04-26 21:50:40 +02:00
ceaed32e94 Fix typo in method name 2017-04-26 21:47:31 +02:00
fba61390d8 Use null-coalesce operator 2017-04-24 22:32:40 +02:00
7bdb55f9a8 Another try at HHVM 2017-04-24 22:26:31 +02:00
b35301659b Try to enable php7 mode for hhvm on travis 2017-04-24 22:22:54 +02:00
60f1504978 Drop emulative lexer implementation
Nothing to emulate anymore... Revert this commit when something
turns up.
2017-04-24 22:15:50 +02:00
aa75838a5f Bump minimum PHP version to 7.0 2017-04-24 22:12:03 +02:00
5f7070e94f Added BuilderFactory->concat() method 2017-04-24 21:42:59 +02:00
8f40eec122 Add BuilderFactory->val() method 2017-04-24 21:28:03 +02:00
e4f493cd1d Replace func_get_args() with variadics
We're on PHP 5.6 now, so can do this...
2017-04-24 21:19:08 +02:00
7419649eae Change BuilderAbstract into BuilderHelpers static class
Used as poor man's function namespace here.
2017-04-24 21:15:11 +02:00
6b6c903585 Kill the BuilderAbstract::$flags property
Instead pass and return the flags explicitly.
2017-04-24 21:06:54 +02:00
9dc93aafaa Add ClassMethod::isMagic() method 2017-04-23 13:54:17 +02:00
ba85da88a0 Merge branch '3.x' 2017-04-19 11:20:32 +02:00
901b895c02 Fix spelling of VISIBILITY_MODIFIER_MASK 2017-04-19 11:20:05 +02:00
55be521ff8 Merge branch '3.x'
Conflicts:
	lib/PhpParser/Builder/Param.php
2017-04-19 11:17:52 +02:00
c877c1a64f Add Builder\Param::makeVariadic() 2017-04-19 11:16:29 +02:00
bc75ac2990 Merge branch '3.x' 2017-04-09 19:51:18 +02:00
c3cbf07946 Pretty printer: Preserve comments in arrays and calls
If call arguments or array contains comments, print it in multiline
form, so that comments may be preserved.
2017-04-09 19:49:47 +02:00
e15b6aa3e0 Fix links 2017-04-09 18:51:46 +02:00
12034b19d7 Use the correct method from NodeVisitor interface (#373)
FAQ's code examples use incorrect method, so this update fixes it
2017-04-09 11:29:07 +02:00
369a078c54 Add FAQ with entries for parent and siblings 2017-04-08 23:27:43 +02:00
deb64ab676 Remove XML serialization from docs
This is no longer available in version 4.
2017-04-08 23:12:53 +02:00
1ec5591574 Tweak node insertion formatting 2017-04-08 23:11:46 +02:00
291bb27f6a Removes unneeded code from JSON encoding example (#372) 2017-04-07 16:15:46 +02:00
b5935a4aff Fix a typo in NodeDumper for REQUIRE_ONCE (#367) 2017-03-17 11:35:48 +01:00
9d680b24f0 Merge branch '3.x' 2017-03-05 19:25:08 +01:00
510e0cd202 Run on Trusty Beta Container (#363)
This uses a more recent HHVM version.
2017-03-05 18:07:48 +01:00
2beb4e7fd6 Merge branch '3.x'
Conflicts:
	lib/PhpParser/ParserAbstract.php
	test/code/parser/errorHandling/recovery.test
2017-02-26 23:47:27 +01:00
da97f78e25 Merge branch '3.x'
Conflicts:
	lib/PhpParser/ParserAbstract.php
	test/code/parser/errorHandling/recovery.test
2017-02-26 23:40:32 +01:00
f291a19fd5 Merge branch '3.x'
Conflicts:
	lib/PhpParser/Parser/Php7.php
2017-02-26 23:13:21 +01:00
dce34f37db Merge branch '3.x' 2017-02-26 22:51:27 +01:00
9cd9c0cd52 Drop XML serialization from php-parse
The class no longer exists, so this broke the script.
2017-02-23 21:18:04 +01:00
9857a545e2 Remove unused variables (#357) 2017-02-11 11:05:36 +01:00
54b61ebe7c Merge branch '3.x' 2017-02-10 21:20:30 +01:00
329e90c239 Improve doc comments 2017-02-09 20:49:52 +01:00
9f5ec5a69a Merge branch '3.x'
Conflicts:
	lib/PhpParser/Parser/Php5.php
	lib/PhpParser/Parser/Php7.php
	test/code/parser/stmt/class/name.test
2017-02-09 20:38:33 +01:00
4a434fdc1b Merge branch '3.x' 2017-02-09 00:03:37 +01:00
f8a2f6e760 Merge branch '3.x' 2017-02-08 23:55:16 +01:00
865bfb2acf Merge branch '3.x'
Conflicts:
	grammar/php7.y
	lib/PhpParser/Parser/Php7.php
	test/code/parser/expr/uvs/globalNonSimpleVarError.test
	test/code/parser/stmt/namespace/groupUseErrors.test
2017-02-05 17:50:54 +01:00
f4ea0270c8 Mark version 2.x as unsupported
No commits to this branch for five months, mark as unsupported.
2017-02-05 14:19:54 +01:00
8f623fb241 Use closures instead of methods for semantic actions
The dispatch using $this->{'reduceRule' . $rule}() is very expensive
because it involves
 * One allocation when converting $rule to a string
 * Another allocation when concatenating the two strings
 * Lowercasing during the method call
 * Various uncached method checks, e.g. visibility.

Using an array of closures for semantic action dispatch is
significantly more efficient.
2017-02-05 11:58:45 +01:00
3bbf8d8f7a Use local var for $stackPos
We're accessing $this->stackPos a lot, and property accesses are
much more expensive than local variable acesses. Its cheaper to
use a local var and pass it as an argument to semantic actions.
2017-02-05 11:57:21 +01:00
90b6d7cb5b Remove XML serializer 2017-02-04 01:10:09 +01:00
7fa5495d64 Merge branch '3.x' 2017-02-03 22:59:06 +01:00
987c61e935 Drop support for false return value in NodeTraverser 2017-02-03 22:36:57 +01:00
2b72bae3d9 Throw if NodeVisitor returns invalid value 2017-02-03 22:30:26 +01:00
2b6804aa50 Throw on nested array in NodeTraverser
The AST never uses deeply nested arrays, so don't support this in
the node traverser. Throw an exception instead.
2017-02-03 22:11:31 +01:00
d9911c8da5 Merge branch '3.x'
Conflicts:
	lib/PhpParser/Node/Expr/ClassConstFetch.php
	test/PhpParser/PrettyPrinterTest.php
2017-02-03 21:54:48 +01:00
79afd56565 Add NodeFinder class
To simplify basic node finding operations.
2017-01-29 23:20:53 +01:00
5cc2750ebc Merge branch '3.x'
Conflicts:
	lib/PhpParser/NodeVisitor.php
2017-01-29 22:36:33 +01:00
e072c789d1 Merge branch '3.x'
Conflicts:
	test/PhpParser/PrettyPrinterTest.php
2017-01-29 21:57:37 +01:00
bfea338d36 Update doc comments after previous comment
Make some of the type annotations more accurate, and complete the
generated doc-comments to be complete (with description and
parameter annotations.)
2017-01-26 00:16:54 +01:00
e3b87f40aa Add non-void return types 2017-01-25 23:32:50 +01:00
70f86cb6cb Update NodeVisitor doc comments 2017-01-25 23:28:44 +01:00
13304d5991 Drop attribute poisoning in parser 2017-01-23 01:13:34 +01:00
7b441d2142 Fix issues reported by psalm 2017-01-23 01:06:46 +01:00
cb5dd28985 Don't include whitespace directly in catch/finally print 2017-01-21 21:25:48 +01:00
5e565e8046 Support insertion of nullable nodes
Still incomplete in some places and the formatting is not always
ideal.
2017-01-21 21:20:42 +01:00
b9b6aeeed9 Support format-preserving node removal
Take care of stripping surrouding tokens appropriately.
2017-01-21 17:52:50 +01:00
0607450f78 Add FinderVisitor
Allows finding nodes based on a filter callback.
2017-01-21 15:41:24 +01:00
aea3a9efe4 Add @property annotations for namespacedName 2017-01-20 23:45:54 +01:00
414adfb146 Drop useNopStatements option 2017-01-20 22:29:41 +01:00
b5fb6f2d0a Don't require useNopStatements=false for format preservation
Instead assign attributes on Nop nodes and in the pretty printer
specially handle end<start offsets. It's a somewhat weird case,
but not wrong per se given the meaning the offsets have.
2017-01-20 22:27:51 +01:00
670ab2f178 Add UPGRADING note 2017-01-19 23:49:18 +01:00
48d3243abe Drop last vestiges of consistent var mode flag 2017-01-19 23:46:25 +01:00
d7f3c4f9d3 Renamve Param::$name to Param::$var
As it now contains a Variable node.
2017-01-19 23:39:15 +01:00
a79306ccd9 Rename StaticVar::$name to $var
As it now holds a Variable
2017-01-19 23:35:31 +01:00
6238f5f9f9 Adjust code for constitent var mode 2017-01-19 23:32:49 +01:00
67274b9594 Enforce useConsistentVariableNames 2017-01-19 23:24:43 +01:00
11b44c15b5 Allow failures on HHVM
Yeah, not gonna deal with this... The combination of an outdated
HHVM version and a shitload of tokenizer bugs makes this not
worthwhile.
2017-01-19 23:05:13 +01:00
7001116be5 Try to use a newer HHVM version 2017-01-19 22:55:52 +01:00
ced914a3b7 Update doc comments to be more specific
Now $stmts arrays really only contains Stmt nodes.
2017-01-19 22:49:18 +01:00
7623d20f69 Automatically wrap in Stmt\Expression in builders 2017-01-19 22:39:21 +01:00
33552764ad Perform manual test updates 2017-01-19 22:31:29 +01:00
953f8c9631 Perform automated test update 2017-01-19 22:25:22 +01:00
b0c962911e Add test porting infrastructure 2017-01-19 22:24:48 +01:00
1bfbd7bcc8 Make useExpressionStatement only supported mode 2017-01-19 21:15:26 +01:00
065c720c28 Merge branch 'formatPreservingPrint' 2017-01-19 21:06:32 +01:00
5ff2519e1e Add UPGRADE-4.0 stub 2017-01-19 21:04:44 +01:00
0c9c8d58ab Drop deprecated $type subnodes
These have been replaced by $flags in 3.0
2017-01-19 21:00:44 +01:00
3b4dd387b8 Comment out code for handling new keywords 2017-01-19 20:58:45 +01:00
61574a1818 Drop support for PHP 5.5 2017-01-19 20:55:08 +01:00
be2ed243f6 Bump development version to 4.0 2017-01-19 20:48:57 +01:00
f21309f52f Add NameResolver mode that does not modify nodes 2016-12-26 21:45:33 +01:00
f581318dd5 Remove leftover file 2016-12-26 18:34:50 +01:00
4d2a4d02b0 Add first shot at format preserving pretty printer 2016-12-26 18:28:49 +01:00
9b2d35d1ac Add expression statement mode 2016-12-24 23:54:24 +01:00
0f582e1708 Add VarLikeIdentifier
For representing Identifiers that have an implicit leading $.

With this done, maybe go one step further?
 * Rename VarLikeIdentifier -> VarIdentifier / VarName
 * Use VarIdentifier / VarName also as an inner node in Variable.
   Not sure if this adds any real value.
2016-12-23 12:39:27 +01:00
d32d937d47 Use Identifier for property names as well 2016-12-23 00:25:45 +01:00
f46c909b5a Represent prop in static prop fetch using Identifier 2016-12-23 00:22:30 +01:00
a947e731c3 Add useConsistentVariableNodes mode
The parameter case is a bit weird, because the subnode is called
"name" here, rather than "var". Nothing we can do about that in
this version though.

The two parser options might be merged. I've kept it separate,
because I think this variable representation should become the
default (or even only representation) in the future, while I'm
less sure about the Identifier thing.
2016-12-23 00:10:59 +01:00
122f449960 Represent builtin types using Identifier as well 2016-12-22 22:23:30 +01:00
6bcc6c31dd Add useIdentifierNodes mode to parser
In this mode non-namespaced names that are currently represented
using strings will be represented using Identifier nodes instead.
Identifier nodes have a string $name subnode and coerce to string.

This allows preserving attributes and in particular location
information on identifiers.
2016-12-22 21:15:44 +01:00
427 changed files with 21728 additions and 13726 deletions

View File

@ -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

View File

@ -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).**

View File

@ -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
View File

@ -3,97 +3,223 @@ PHP Parser
[![Build Status](https://travis-ci.org/nikic/PHP-Parser.svg?branch=master)](https://travis-ci.org/nikic/PHP-Parser) [![Coverage Status](https://coveralls.io/repos/github/nikic/PHP-Parser/badge.svg?branch=master)](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

View File

@ -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
View 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.

View File

@ -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];
}

View File

@ -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"
}
}
}

View File

@ -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)

View File

@ -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;
}
}
}

View File

@ -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>
```

View File

@ -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
View 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

View 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.

View 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;
}
```

View File

@ -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:

View 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);
}
}
```

View 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`.

View File

@ -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.

View 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.

View 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.

View 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.

View 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.

View File

@ -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

View File

@ -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;

View File

@ -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; }
;
%%

View File

@ -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; }
;
%%

View File

@ -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\', []))); }';
}

View File

@ -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;
}
}
}
}

View File

@ -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;
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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
);
}
}

View File

@ -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
);
}
}
}

View File

@ -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
);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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');
}
}

View 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;
}
}

View File

@ -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,
];
}
}
}

View File

@ -1,7 +1,7 @@
<?php
<?php declare(strict_types=1);
namespace PhpParser\Comment;
class Doc extends \PhpParser\Comment
{
}
}

View File

@ -0,0 +1,6 @@
<?php
namespace PhpParser;
class ConstExprEvaluationException extends \Exception
{}

View 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);
}
}

View File

@ -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');
}

View File

@ -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);
}
}

View File

@ -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 = [];
}
}
}

View File

@ -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;
}
}
}

View 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;
}
}

View 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;
}
}

View 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'];
}
}

View 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;
}
}

View 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\"");
}
}

View File

@ -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;

View File

@ -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 */
}

View 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;
}
}

View File

@ -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);
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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
{
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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'];
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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;
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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