Compare commits

..

32 Commits

Author SHA1 Message Date
Nikita Popov
571ca90b7e Release PHP-Parser 5.0.0-alpha3 2023-06-24 17:48:52 +02:00
Nikita Popov
16c766eae1 Don't take subnodes by reference when traversing
Explicitly assign the property where necessary to avoid the
creation of unnecessary reference-wrappers everywhere.
2023-05-28 21:54:07 +02:00
Nikita Popov
53f6717329 Remove unnecessary Node return value from traverseNode()
The node always stays the same: We may only change subnodes.
2023-05-28 21:51:45 +02:00
Nikita Popov
5b65f9fc92 Some documentation updates 2023-05-27 21:35:10 +02:00
Nikita Popov
69993a181a Update CHANGELOG and UPGRADING 2023-05-27 16:26:36 +02:00
Nikita Popov
23647573e8 Represent names using string rather than array of parts
In most circumstances we are interested in the whole string, not
the parts split by namespace separator. As names are common, this
representation measurably improves memory usage and performance.
2023-05-21 21:25:49 +02:00
Nikita Popov
df3a7057ab Add Name::getParts(), deprecate Name::$parts
In preparation for switching this to a plain string in
PHP-Parser 5, deprecate direct access to the property and
provide an API that will work on both versions.

(cherry picked from commit c9e5a13d68)
2023-05-21 21:24:13 +02:00
Nikita Popov
d43edfbb31 Support CRLF newlines in pretty printer
Can be enabled using the "newlines" option.
2023-05-21 20:56:56 +02:00
Nikita Popov
ad8daa12b2 Normalize newlines in Comment::getReformattedText()
Normalize CRLF to LF in getReformattedText(). That was, if the
comment is pretty-printed, we will use proper LF newlines, rather
than inserting indentation between the CR and LF.

At the same time, this also makes it easier to emit actual CRLF
newlines with a custom pretty printer.

Fixes .
2023-05-21 17:38:56 +02:00
Nikita Popov
5883189d61 Fix return type of Comment::getReformattedText() 2023-05-21 15:51:27 +02:00
Nikita Popov
afe1628a72 Add support for NodeVisitor::REPLACE_WITH_NULL
Fixes .
2023-05-21 15:41:41 +02:00
Nikita Popov
289756d056 Gracefully handle non-contiguous arrays in Differ
Fixes .
2023-05-21 15:15:36 +02:00
Nikita Popov
8490c0e82d Call leaveNode() on visitors in reverse order
Node visitation is now properly nested. The call sequence will
now be

    $visitor1->enterNode($n);
    $visitor2->enterNode($n);
    $visitor2->leaveNode($n);
    $visitor1->leaveNode($n);

rather than

    $visitor1->enterNode($n);
    $visitor2->enterNode($n);
    $visitor1->leaveNode($n);
    $visitor2->leaveNode($n);

Fixes .
2023-05-21 12:31:15 +02:00
Nikita Popov
8bc698248d Ensure removing visitor does not leave holes 2023-05-21 12:07:21 +02:00
Nikita Popov
fb2c3ac97c Fix emulative lexer with default error handler
If no error handler is provided, explicitly create one, so we don't
end up calling handleError() on null.
2023-05-20 22:26:53 +02:00
Nikita Popov
c23976a299 Stop accepting strings as types
For types the use of a string is ambiguous -- it could be either
an Identifier or a Name. Don't guess.

Retain the implicit promotion to Identifier in places where only
Identifier is legal, e.g. various symbol names.
2023-05-20 22:13:04 +02:00
Nikita Popov
a9dad5c54e Fix type of ClassConst::$type 2023-05-20 22:01:01 +02:00
Nikita Popov
74caed6446 Fix labelCharMap for DEL character
This map is supposed to have string keys, not integer keys.
2023-05-20 21:55:53 +02:00
Nikita Popov
a5d4c1005c Remove some unused symbols 2023-05-20 21:40:55 +02:00
Nikita Popov
93731c5cfa Move constants from NodeTraverser to NodeVisitor
These are really part of the NodeVisitor API. Retain aliases for
compatibility.
2023-05-20 21:37:34 +02:00
Nikita Popov
0a8a333a4a Blacklist test with comments in intersection types 2023-05-20 21:21:45 +02:00
Nikita Popov
91da19147b Support readonly anonymous classes 2023-05-20 21:17:44 +02:00
Nikita Popov
5c267f55c9 Add support for typed constants
RFC: https://wiki.php.net/rfc/typed_class_constants
2023-05-20 21:02:03 +02:00
Nikita Popov
9a5d5c112c Add newline at end of file for many tests
Add the newline in reconstructTest() and run updateTests.php, to
reduce spurious diffs in the future.
2023-05-20 19:18:11 +02:00
Nikita Popov
779b6950c3 Fix coding style 2023-05-20 18:55:12 +02:00
Nikita Popov
b68fb76f14 Add makeReadonly() to param builder
(cherry picked from commit 11e2dcd96c)
2023-05-19 22:17:50 +02:00
Cees-Jan Kiewiet
36a6dcd04e [5.x] Add constructor property promotion
By making flags on the Param builder configurable by providing make(Public|Protected|Private) methods we can promote parameters to properties from the constructor
2023-03-06 23:12:51 +01:00
Nikita Popov
bb4263ea1a Treat del as label character depending on PHP version
In formatting-preserving pretty printing, treat DEL as a label
character based on the target PHP version (the default of 7.1
implying it isn't one).

This avoids failure to round-trip an unchanged input.
2023-03-05 17:03:00 +01:00
Nikita Popov
4ce0de2d12 Use PHP 7.1 as default pretty printer target 2023-03-05 16:56:50 +01:00
Nikita Popov
a3bc900a41 Don't ltrim when preserving formatting
We shouldn't ltrim when printing a whole file, that way we will
not just fail to preserve formatting, but actually change semantics
by dropping meaningful whitespace.
2023-03-05 16:42:30 +01:00
Nikita Popov
7785d2b887 Remove garbage collection section from docs
The GC issue has been fixed in PHP 7.3 and is no longer relevant.
2023-03-05 16:12:50 +01:00
Nikita Popov
aa721520f9 Don't generate braces for "else if"
Mentioned in .
2023-03-05 11:30:39 +01:00
205 changed files with 4104 additions and 4171 deletions
CHANGELOG.mdREADME.mdUPGRADE-5.0.md
doc
grammar
lib/PhpParser
test
PhpParser
code
formatPreservation
parser
blockComments.testcomments.test
errorHandling
expr
nopPositions.test
scalar
semiReserved.test
stmt
prettyPrinter
test_old
tools/fuzzing

@@ -1,3 +1,37 @@
Version 5.0.0-alpha3 (2023-06-24)
---------------------------------
See UPGRADE-5.0 for detailed migration instructions.
### Added
* [PHP 8.3] Added support for typed constants.
* [PHP 8.3] Added support for readonly anonymous classes.
* Added support for `NodeVisitor::REPLACE_WITH_NULL`.
* Added support for CRLF newlines in the pretty printer, using the new `newline` option.
### Changed
* Use PHP 7.1 as the default target version for the pretty printer.
* Print `else if { }` instead of `else { if { } }`.
* The `leaveNode()` method on visitors is now invoked in reverse order of `enterNode()`.
* Moved `NodeTraverser::REMOVE_NODE` etc. to `NodeVisitor::REMOVE_NODE`. The old constants are still
available for compatibility.
* The `Name` subnode `parts` has been replaced by `name`, which stores the name as a string rather
than an array of parts separated by namespace separators. The `getParts()` method returns the old
representation.
* No longer accept strings for types in Node constructors. Instead, either an `Identifier`, `Name`
or `ComplexType` must be passed.
* `Comment::getReformattedText()` now normalizes CRLF newlines to LF newlines.
### Fixed
* Don't trim leading whitespace in formatting preserving printer.
* Treat DEL as a label character in the formatting preserving printer depending on the targeted
PHP version.
* Fix error reporting in emulative lexer without explicitly specified error handler.
* Gracefully handle non-contiguous array indices in the `Differ`.
Version 5.0.0-alpha2 (2023-03-05)
---------------------------------

@@ -70,12 +70,17 @@ This dumps an AST looking something like this:
```
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: test
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
variadic: false
@@ -90,12 +95,11 @@ array(
0: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: var_dump
)
name: var_dump
)
args: array(
0: Arg(
name: null
value: Expr_Variable(
name: foo
)
@@ -137,12 +141,16 @@ This gives us an AST where the `Function_::$stmts` are empty:
```
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: test
)
params: array(
0: Param(
attrGroups: array(
)
type: null
byRef: false
variadic: false

@@ -71,6 +71,17 @@ Now, destructuring is always represented using `Node\Expr\List_`. The `kind` att
`Node\Expr\List_::KIND_LIST` or `Node\Expr\List_::KIND_ARRAY` specifies which syntax was actually
used.
### Changes to the name representation
Previously, `Name` nodes had a `parts` subnode, which stores an array of name parts, split by
namespace separators. Now, `Name` nodes instead have a `name` subnode, which stores a plain string.
For example, the name `Foo\Bar` was previously represented by `Name(parts: ['Foo', 'Bar'])` and is
now represented by `Name(name: 'Foo\Bar')` instead.
It is possible to convert the name to the previous representation using `$name->getParts()`. The
`Name` constructor continues to accept both the string and the array representation.
### Renamed nodes
A number of AST nodes have been renamed or moved in the AST hierarchy:
@@ -104,6 +115,21 @@ PhpParser\Node\Stmt\Class_::MODIFIER_READONLY -> PhpParser\Modifiers::READONLY
PhpParser\Node\Stmt\Class_::VISIBILITY_MODIFIER_MASK -> PhpParser\Modifiers::VISIBILITY_MASK
```
### Changes to node constructors
Node constructor arguments accepting types now longer accept plain strings. Either an `Identifier` or `Name` (or `ComplexType`) should be passed instead. This affects the following constructor arguments:
* The `'returnType'` key of `$subNodes` argument of `Node\Expr\ArrowFunction`.
* The `'returnType'` key of `$subNodes` argument of `Node\Expr\Closure`.
* The `'returnType'` key of `$subNodes` argument of `Node\Stmt\ClassMethod`.
* The `'returnType'` key of `$subNodes` argument of `Node\Stmt\Function_`.
* The `$type` argument of `Node\NullableType`.
* The `$type` argument of `Node\Param`.
* The `$type` argument of `Node\Stmt\Property`.
* The `$type` argument of `Node\ClassConst` (new in PHP-Parser 5.0, listed for completeness only).
To follow the previous behavior, an `Identifier` should be passed, which indicates a built-in type.
### Changes to the pretty printer
A number of changes to the standard pretty printer have been made, to make it match contemporary coding style conventions (and in particular PSR-12). Options to restore the previous behavior are not provided, but it is possible to override the formatting methods (such as `pStmt_ClassMethod`) with your preferred formatting.
@@ -156,12 +182,27 @@ Backslashes in single-quoted strings are now only printed if they are necessary:
'\\\\';
```
The pretty printer now accepts a `phpVersion` option, which accepts a `PhpVersion` object and defaults to PHP 7.0. The pretty printer will make formatting choices to make the code valid for that version. It currently controls the following behavior:
`else if` structures will now omit redundant parentheses:
```php
# Before
else {
if ($x) {
// ...
}
}
# After
else if ($x) {
// ...
}
```
The pretty printer now accepts a `phpVersion` option, which accepts a `PhpVersion` object and defaults to PHP 7.1. The pretty printer will make formatting choices to make the code valid for that version. It currently controls the following behavior:
* For PHP >= 7.0 (default), short array syntax `[]` will be used by default. This does not affect nodes that specify an explicit array syntax using the `kind` attribute.
* For PHP >= 7.0 (default), parentheses around `yield` expressions will only be printed when necessary. Previously, parentheses were always printed, even if `yield` was used as a statement.
* For PHP >= 7.1, the short array syntax `[]` will be used for destructuring by default (instead of
`list()`). This does not affect nodes that specify and explicit syntax using the `kind` attribute.
* For PHP >= 7.1 (default), the short array syntax `[]` will be used for destructuring by default (instead of `list()`). This does not affect nodes that specify and explicit syntax using the `kind` attribute.
* For PHP >= 7.3, a newline is no longer forced after heredoc/nowdoc strings, as the requirement for this has been removed with the introduction of flexible heredoc/nowdoc strings.
### Changes to precedence handling in the pretty printer
@@ -197,6 +238,33 @@ protected function pExpr_UnaryPlus(
The new `$precedence` and `$lhsPrecedence` arguments need to be passed down to the `pInfixOp()`, `pPrefixOp()` and `pPostfixOp()` methods.
### Changes to the node traverser
If there are multiple visitors, the node traverser will now call `leaveNode()` and `afterTraverse()` methods in the reverse order of the corresponding `enterNode()` and `beforeTraverse()` calls:
```php
# Before
$visitor1->enterNode($node);
$visitor2->enterNode($node);
$visitor1->leaveNode($node);
$visitor2->leaveNode($node);
# After
$visitor1->enterNode($node);
$visitor2->enterNode($node);
$visitor2->leaveNode($node);
$visitor1->leaveNode($node);
```
Additionally, the special `NodeVisitor` return values have been moved from `NodeTraverser` to `NodeVisitor`. The old names are deprecated, but still available.
```php
PhpParser\NodeTraverser::REMOVE_NODE -> PhpParser\NodeVisitor::REMOVE_NODE
PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN -> PhpParser\NodeVisitor::DONT_TRAVERSE_CHILDREN
PhpParser\NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN -> PhpParser\NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN
PhpParser\NodeTraverser::STOP_TRAVERSAL -> PhpParser\NodeVisitor::STOP_TRAVERSAL
```
### Changes to token representation
Tokens are now internally represented using the `PhpParser\Token` class, which exposes the same base interface as
@@ -217,7 +285,8 @@ class Token {
The `Lexer::getTokens()` method will now return an array of `Token`s, rather than an array of arrays and strings.
Additionally, the token array is now terminated by a sentinel token with ID 0.
### Other removed functionality
### Miscellaneous changes
* The deprecated `Builder\Param::setTypeHint()` method has been removed in favor of `Builder\Param::setType()`.
* The deprecated `Error` constructor taking a start line has been removed. Pass `['startLine' => $startLine]` attributes instead.
* `Comment::getReformattedText()` now normalizes CRLF newlines to LF newlines.

@@ -96,12 +96,17 @@ For the sample code from the previous section, this will produce the following o
```
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: printLine
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
variadic: false
@@ -129,12 +134,11 @@ array(
1: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: printLine
)
name: printLine
)
args: array(
0: Arg(
name: null
value: Scalar_String(
value: Hello World!!!
)
@@ -343,15 +347,18 @@ i.e. before its subnodes are traversed, the latter when it is left.
All four methods can either return the changed node or not return at all (i.e. `null`) in which
case the current node is not changed.
The `enterNode()` method can additionally return the value `NodeTraverser::DONT_TRAVERSE_CHILDREN`,
The `enterNode()` method can additionally return the value `NodeVisitor::DONT_TRAVERSE_CHILDREN`,
which instructs the traverser to skip all children of the current node. To furthermore prevent subsequent
visitors from visiting the current node, `NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN` can be used instead.
visitors from visiting the current node, `NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN` can be used instead.
Both methods can additionally return the value `NodeTraverser::REMOVE_NODE`, in which
case the current node will be removed from the parent array. Furthermore, it is possible to return
an array of nodes, which will be merged into the parent array at the offset of the current node.
I.e. if in `array(A, B, C)` the node `B` should be replaced with `array(X, Y, Z)` the result will
be `array(A, X, Y, Z, C)`.
Both methods can additionally return the following values:
* `NodeVisitor::STOP_TRAVERSAL`, in which case no further nodes will be visited.
* `NodeVisitor::REMOVE_NODE`, in which case the current node will be removed from the parent array.
* `NodeVisitor::REPLACE_WITH_NULL`, in which case the current node will be replaced with `null`.
* An array of nodes, which will be merged into the parent array at the offset of the current node.
I.e. if in `array(A, B, C)` the node `B` should be replaced with `array(X, Y, Z)` the result will
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.
@@ -493,7 +500,7 @@ 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;
use PhpParser\NodeVisitor;
class NodeVisitor_NamespaceConverter extends \PhpParser\NodeVisitorAbstract
{
@@ -513,7 +520,7 @@ class NodeVisitor_NamespaceConverter extends \PhpParser\NodeVisitorAbstract
return $node->stmts;
} elseif ($node instanceof Stmt\Use_) {
// remove use nodes altogether
return NodeTraverser::REMOVE_NODE;
return NodeVisitor::REMOVE_NODE;
}
}
}

@@ -42,24 +42,3 @@ When possible, objects should be reused rather than being newly instantiated for
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.

@@ -32,10 +32,11 @@ Customizing the formatting
--------------------------
The pretty printer respects a number of `kind` attributes used by some notes (e.g., whether an
integer should be printed as decimal, hexadecimal, etc). Additionally, it supports two options:
integer should be printed as decimal, hexadecimal, etc). Additionally, it supports three options:
* `phpVersion` (defaults to 7.0) allows opting into formatting that is not supported by older PHP
* `phpVersion` (defaults to 7.1) allows opting into formatting that is not supported by older PHP
versions.
* `newline` (defaults to `"\n"`) can be set to `"\r\n"` in order to produce Windows newlines.
* `shortArraySyntax` determines the used array syntax if the `kind` attribute is not set. This is
a legacy option, and `phpVersion` should be used to control this behavior instead.

@@ -129,13 +129,13 @@ Now `$a && $b` will be replaced by `!($a && $b)`. Then the traverser will go int
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, there are two special replacement types. The first is removal of a node:
Finally, there are three special replacement types. 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;
return NodeVisitor::REMOVE_NODE;
}
}
```
@@ -155,7 +155,7 @@ public function leaveNode(Node $node) {
&& $node->expr->name instanceof Node\Name
&& $node->expr->name->toString() === 'var_dump'
) {
return NodeTraverser::REMOVE_NODE;
return NodeVisitor::REMOVE_NODE;
}
}
```
@@ -164,6 +164,20 @@ This example will remove all calls to `var_dump()` which occur as expression sta
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).
Another way to remove nodes is to replace them with `null`. For example, all `else` statements could
be removed as follows:
```php
public function leaveNode(Node $node) {
if ($node instanceof Node\Stmt\Else_) {
return NodeVisitor::REPLACE_WITH_NULL;
}
}
```
This is only safe to do if the subnode the node is stored in is nullable. `Node\Stmt\Else_` only
occurs inside `Node\Stmt\If_::$else`, which is nullable, so this particular replacement is safe.
Next to removing nodes, it is also possible to replace one node with multiple nodes. This
only works if the parent structure is an array.
@@ -197,7 +211,7 @@ private $classes = [];
public function enterNode(Node $node) {
if ($node instanceof Node\Stmt\Class_) {
$this->classes[] = $node;
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
return NodeVisitor::DONT_TRAVERSE_CHILDREN;
}
}
```
@@ -217,7 +231,7 @@ public function enterNode(Node $node) {
$node->namespacedName->toString() === 'Foo\Bar\Baz'
) {
$this->class = $node;
return NodeTraverser::STOP_TRAVERSAL;
return NodeVisitor::STOP_TRAVERSAL;
}
}
```
@@ -255,13 +269,14 @@ $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)
$visitorA->leaveNode(Expr_Variable)
$visitorB->leaveNode(Stmt_Return)
$visitorA->leaveNode(Stmt_Return)
```
That is, when visiting a node, enterNode and leaveNode will always be called for all visitors.
That is, when visiting a node, `enterNode()` and `leaveNode()` will always be called for all
visitors, with the `leaveNode()` calls happening in the reverse order of the `enterNode()` calls.
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:
@@ -286,6 +301,7 @@ special enterNode/leaveNode return values:
* 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 `REPLACE_WITH_NULL`, 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.

@@ -340,7 +340,10 @@ non_empty_class_const_list:
;
class_const:
identifier_maybe_reserved '=' expr { $$ = Node\Const_[$1, $3]; }
T_STRING '=' expr
{ $$ = Node\Const_[new Node\Identifier($1, stackAttributes(#1)), $3]; }
| semi_reserved '=' expr
{ $$ = Node\Const_[new Node\Identifier($1, stackAttributes(#1)), $3]; }
;
inner_statement_list_ex:
@@ -842,6 +845,9 @@ class_statement:
| optional_attributes method_modifiers T_CONST class_const_list semi
{ $$ = new Stmt\ClassConst($4, $2, attributes(), $1);
$this->checkClassConst($$, #2); }
| optional_attributes method_modifiers T_CONST type_expr class_const_list semi
{ $$ = new Stmt\ClassConst($5, $2, attributes(), $1, $4);
$this->checkClassConst($$, #2); }
| optional_attributes method_modifiers T_FUNCTION optional_ref identifier_maybe_reserved '(' parameter_list ')'
optional_return_type method_body
{ $$ = Stmt\ClassMethod[$5, ['type' => $2, 'byRef' => $4, 'params' => $7, 'returnType' => $9, 'stmts' => $10, 'attrGroups' => $1]];
@@ -1070,8 +1076,8 @@ expr:
;
anonymous_class:
optional_attributes T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}'
{ $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]], $3);
optional_attributes class_entry_type ctor_arguments extends_from implements_list '{' class_statement_list '}'
{ $$ = array(Stmt\Class_[null, ['type' => $2, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]], $3);
$this->checkClass($$[0], -1); }
;

@@ -22,6 +22,8 @@ class ClassConst implements PhpParser\Builder {
/** @var list<Node\AttributeGroup> */
protected $attributeGroups = [];
/** @var Identifier|Node\Name|Node\ComplexType */
protected $type;
/**
* Creates a class constant builder
@@ -119,6 +121,19 @@ class ClassConst implements PhpParser\Builder {
return $this;
}
/**
* Sets the constant type.
*
* @param string|Node\Name|Identifier|Node\ComplexType $type
*
* @return $this
*/
public function setType($type) {
$this->type = BuilderHelpers::normalizeType($type);
return $this;
}
/**
* Returns the built class node.
*
@@ -129,7 +144,8 @@ class ClassConst implements PhpParser\Builder {
$this->constants,
$this->flags,
$this->attributes,
$this->attributeGroups
$this->attributeGroups,
$this->type
);
}
}

@@ -4,6 +4,7 @@ namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Modifiers;
use PhpParser\Node;
class Param implements PhpParser\Builder {
@@ -15,6 +16,8 @@ class Param implements PhpParser\Builder {
protected $type = null;
/** @var bool */
protected $byRef = false;
/** @var int */
protected $flags = 0;
/** @var bool */
protected $variadic = false;
/** @var list<Node\AttributeGroup> */
@@ -80,6 +83,50 @@ class Param implements PhpParser\Builder {
return $this;
}
/**
* Makes the (promoted) parameter public.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePublic() {
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PUBLIC);
return $this;
}
/**
* Makes the (promoted) parameter protected.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeProtected() {
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PROTECTED);
return $this;
}
/**
* Makes the (promoted) parameter private.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePrivate() {
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::PRIVATE);
return $this;
}
/**
* Makes the (promoted) parameter readonly.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeReadonly() {
$this->flags = BuilderHelpers::addModifier($this->flags, Modifiers::READONLY);
return $this;
}
/**
* Adds an attribute group.
*
@@ -101,7 +148,7 @@ class Param implements PhpParser\Builder {
public function getNode(): Node {
return new Node\Param(
new Node\Expr\Variable($this->name),
$this->default, $this->type, $this->byRef, $this->variadic, [], 0, $this->attributeGroups
$this->default, $this->type, $this->byRef, $this->variadic, [], $this->flags, $this->attributeGroups
);
}
}

@@ -150,19 +150,21 @@ class Comment implements \JsonSerializable {
*
* "Reformatted" here means that we try to clean up the whitespace at the
* starts of the lines. This is necessary because we receive the comments
* without trailing whitespace on the first line, but with trailing whitespace
* without leading whitespace on the first line, but with leading whitespace
* on all subsequent lines.
*
* @return mixed|string
* Additionally, this normalizes CRLF newlines to LF newlines.
*
* @return string
*/
public function getReformattedText() {
$text = $this->text;
public function getReformattedText(): string {
$text = str_replace("\r\n", "\n", $this->text);
$newlinePos = strpos($text, "\n");
if (false === $newlinePos) {
// Single line comments don't need further processing
return $text;
}
if (preg_match('((*BSR_ANYCRLF)(*ANYCRLF)^.*(?:\R\s+\*.*)+$)', $text)) {
if (preg_match('(^.*(?:\n\s+\*.*)+$)', $text)) {
// Multi line comment of the type
//
// /*
@@ -171,9 +173,9 @@ class Comment implements \JsonSerializable {
// */
//
// is handled by replacing the whitespace sequences before the * by a single space
return preg_replace('(^\s+\*)m', ' *', $this->text);
return preg_replace('(^\s+\*)m', ' *', $text);
}
if (preg_match('(^/\*\*?\s*[\r\n])', $text) && preg_match('(\n(\s*)\*/$)', $text, $matches)) {
if (preg_match('(^/\*\*?\s*\n)', $text) && preg_match('(\n(\s*)\*/$)', $text, $matches)) {
// Multi line comment of the type
//
// /*

@@ -33,6 +33,8 @@ class Differ {
* @return DiffElem[] Diff (edit script)
*/
public function diff(array $old, array $new): array {
$old = \array_values($old);
$new = \array_values($new);
list($trace, $x, $y) = $this->calculateTrace($old, $new);
return $this->extractDiff($trace, $x, $y, $old, $new);
}

@@ -18,6 +18,8 @@ use PhpParser\Node\Expr;
class PrintableNewAnonClassNode extends Expr {
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/** @var int Modifiers */
public $flags;
/** @var (Node\Arg|Node\VariadicPlaceholder)[] Arguments */
public $args;
/** @var null|Node\Name Name of extended class */
@@ -36,11 +38,12 @@ class PrintableNewAnonClassNode extends Expr {
* @param array<string, mixed> $attributes Attributes
*/
public function __construct(
array $attrGroups, array $args, ?Node\Name $extends, array $implements,
array $attrGroups, int $flags, array $args, ?Node\Name $extends, array $implements,
array $stmts, array $attributes
) {
parent::__construct($attributes);
$this->attrGroups = $attrGroups;
$this->flags = $flags;
$this->args = $args;
$this->extends = $extends;
$this->implements = $implements;
@@ -53,7 +56,7 @@ class PrintableNewAnonClassNode extends Expr {
// We don't assert that $class->name is null here, to allow consumers to assign unique names
// to anonymous classes for their own purposes. We simplify ignore the name here.
return new self(
$class->attrGroups, $newNode->args, $class->extends, $class->implements,
$class->attrGroups, $class->flags, $newNode->args, $class->extends, $class->implements,
$class->stmts, $newNode->getAttributes()
);
}
@@ -63,6 +66,6 @@ class PrintableNewAnonClassNode extends Expr {
}
public function getSubNodeNames(): array {
return ['attrGroups', 'args', 'extends', 'implements', 'stmts'];
return ['attrGroups', 'flags', 'args', 'extends', 'implements', 'stmts'];
}
}

@@ -85,6 +85,10 @@ class Emulative extends Lexer {
return;
}
if ($errorHandler === null) {
$errorHandler = new ErrorHandler\Throwing();
}
$this->patches = [];
foreach ($emulators as $emulator) {
$code = $emulator->preprocessCode($code, $this->patches);

@@ -45,7 +45,7 @@ abstract class KeywordEmulator extends TokenEmulator {
public function reverseEmulate(string $code, array $tokens): array {
$keywordToken = $this->getKeywordToken();
foreach ($tokens as $i => $token) {
foreach ($tokens as $token) {
if ($token->id === $keywordToken) {
$token->id = \T_STRING;
}

@@ -30,7 +30,7 @@ class ArrowFunction extends Expr implements FunctionLike {
* static?: bool,
* byRef?: bool,
* params?: Node\Param[],
* returnType?: null|string|Node\Identifier|Node\Name|Node\ComplexType,
* returnType?: null|Node\Identifier|Node\Name|Node\ComplexType,
* attrGroups?: Node\AttributeGroup[]
* } $subNodes Array of the following subnodes:
* 'expr' : Expression body
@@ -46,8 +46,7 @@ class ArrowFunction extends Expr implements FunctionLike {
$this->static = $subNodes['static'] ?? false;
$this->byRef = $subNodes['byRef'] ?? false;
$this->params = $subNodes['params'] ?? [];
$returnType = $subNodes['returnType'] ?? null;
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
$this->returnType = $subNodes['returnType'] ?? null;
$this->expr = $subNodes['expr'];
$this->attrGroups = $subNodes['attrGroups'] ?? [];
}

@@ -31,7 +31,7 @@ class Closure extends Expr implements FunctionLike {
* byRef?: bool,
* params?: Node\Param[],
* uses?: ClosureUse[],
* returnType?: null|string|Node\Identifier|Node\Name|Node\ComplexType,
* returnType?: null|Node\Identifier|Node\Name|Node\ComplexType,
* stmts?: Node\Stmt[],
* attrGroups?: Node\AttributeGroup[],
* } $subNodes Array of the following optional subnodes:
@@ -50,8 +50,7 @@ class Closure extends Expr implements FunctionLike {
$this->byRef = $subNodes['byRef'] ?? false;
$this->params = $subNodes['params'] ?? [];
$this->uses = $subNodes['uses'] ?? [];
$returnType = $subNodes['returnType'] ?? null;
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
$this->returnType = $subNodes['returnType'] ?? null;
$this->stmts = $subNodes['stmts'] ?? [];
$this->attrGroups = $subNodes['attrGroups'] ?? [];
}

@@ -5,8 +5,8 @@ namespace PhpParser\Node;
use PhpParser\NodeAbstract;
class Name extends NodeAbstract {
/** @var string[] Parts of the name */
public $parts;
/** @var string Name as string */
public $name;
/** @var array<string, bool> */
private static $specialClassNames = [
@@ -23,11 +23,20 @@ class Name extends NodeAbstract {
*/
final public function __construct($name, array $attributes = []) {
$this->attributes = $attributes;
$this->parts = self::prepareName($name);
$this->name = self::prepareName($name);
}
public function getSubNodeNames(): array {
return ['parts'];
return ['name'];
}
/**
* Get parts of name (split by the namespace separator).
*
* @return string[] Parts of name
*/
public function getParts(): array {
return \explode('\\', $this->name);
}
/**
@@ -36,7 +45,10 @@ class Name extends NodeAbstract {
* @return string First part of the name
*/
public function getFirst(): string {
return $this->parts[0];
if (false !== $pos = \strpos($this->name, '\\')) {
return \substr($this->name, 0, $pos);
}
return $this->name;
}
/**
@@ -45,7 +57,10 @@ class Name extends NodeAbstract {
* @return string Last part of the name
*/
public function getLast(): string {
return $this->parts[count($this->parts) - 1];
if (false !== $pos = \strrpos($this->name, '\\')) {
return \substr($this->name, $pos + 1);
}
return $this->name;
}
/**
@@ -54,7 +69,7 @@ class Name extends NodeAbstract {
* @return bool Whether the name is unqualified
*/
public function isUnqualified(): bool {
return 1 === count($this->parts);
return false === \strpos($this->name, '\\');
}
/**
@@ -63,7 +78,7 @@ class Name extends NodeAbstract {
* @return bool Whether the name is qualified
*/
public function isQualified(): bool {
return 1 < count($this->parts);
return false !== \strpos($this->name, '\\');
}
/**
@@ -91,7 +106,7 @@ class Name extends NodeAbstract {
* @return string String representation
*/
public function toString(): string {
return implode('\\', $this->parts);
return $this->name;
}
/**
@@ -111,7 +126,7 @@ class Name extends NodeAbstract {
* @return string Lowercased string representation
*/
public function toLowerString(): string {
return strtolower(implode('\\', $this->parts));
return strtolower($this->name);
}
/**
@@ -120,8 +135,7 @@ class Name extends NodeAbstract {
* @return bool Whether identifier is a special class name
*/
public function isSpecialClassName(): bool {
return count($this->parts) === 1
&& isset(self::$specialClassNames[strtolower($this->parts[0])]);
return isset(self::$specialClassNames[strtolower($this->name)]);
}
/**
@@ -131,7 +145,7 @@ class Name extends NodeAbstract {
* @return string String representation
*/
public function __toString(): string {
return implode('\\', $this->parts);
return $this->name;
}
/**
@@ -151,7 +165,16 @@ class Name extends NodeAbstract {
* @return static|null Sliced name
*/
public function slice(int $offset, ?int $length = null) {
$numParts = count($this->parts);
if ($offset === 1 && $length === null) {
// Short-circuit the common case.
if (false !== $pos = \strpos($this->name, '\\')) {
return new static(\substr($this->name, $pos + 1));
}
return null;
}
$parts = \explode('\\', $this->name);
$numParts = \count($parts);
$realOffset = $offset < 0 ? $offset + $numParts : $offset;
if ($realOffset < 0 || $realOffset > $numParts) {
@@ -172,7 +195,7 @@ class Name extends NodeAbstract {
return null;
}
return new static(array_slice($this->parts, $realOffset, $realLength), $this->attributes);
return new static(array_slice($parts, $realOffset, $realLength), $this->attributes);
}
/**
@@ -197,42 +220,42 @@ class Name extends NodeAbstract {
return null;
}
if (null === $name1) {
return new static(self::prepareName($name2), $attributes);
return new static($name2, $attributes);
}
if (null === $name2) {
return new static(self::prepareName($name1), $attributes);
return new static($name1, $attributes);
} else {
return new static(
array_merge(self::prepareName($name1), self::prepareName($name2)), $attributes
self::prepareName($name1) . '\\' . self::prepareName($name2), $attributes
);
}
}
/**
* Prepares a (string, array or Name node) name for use in name changing methods by converting
* it to an array.
* it to a string.
*
* @param string|string[]|self $name Name to prepare
*
* @return string[] Prepared name
* @return string Prepared name
*/
private static function prepareName($name): array {
private static function prepareName($name): string {
if (\is_string($name)) {
if ('' === $name) {
throw new \InvalidArgumentException('Name cannot be empty');
}
return explode('\\', $name);
return $name;
}
if (\is_array($name)) {
if (empty($name)) {
throw new \InvalidArgumentException('Name cannot be empty');
}
return $name;
return implode('\\', $name);
}
if ($name instanceof self) {
return $name->parts;
return $name->name;
}
throw new \InvalidArgumentException(

@@ -2,6 +2,8 @@
namespace PhpParser\Node;
use PhpParser\Node;
class NullableType extends ComplexType {
/** @var Identifier|Name Type */
public $type;
@@ -9,12 +11,12 @@ class NullableType extends ComplexType {
/**
* Constructs a nullable type (wrapping another type).
*
* @param string|Identifier|Name $type Type
* @param Identifier|Name $type Type
* @param array<string, mixed> $attributes Additional attributes
*/
public function __construct($type, array $attributes = []) {
public function __construct(Node $type, array $attributes = []) {
$this->attributes = $attributes;
$this->type = \is_string($type) ? new Identifier($type) : $type;
$this->type = $type;
}
public function getSubNodeNames(): array {

@@ -3,6 +3,7 @@
namespace PhpParser\Node;
use PhpParser\Modifiers;
use PhpParser\Node;
use PhpParser\NodeAbstract;
class Param extends NodeAbstract {
@@ -26,7 +27,7 @@ class Param extends NodeAbstract {
*
* @param Expr\Variable|Expr\Error $var Parameter variable
* @param null|Expr $default Default value
* @param null|string|Identifier|Name|ComplexType $type Type declaration
* @param null|Identifier|Name|ComplexType $type Type declaration
* @param bool $byRef Whether is passed by reference
* @param bool $variadic Whether this is a variadic argument
* @param array<string, mixed> $attributes Additional attributes
@@ -34,14 +35,14 @@ class Param extends NodeAbstract {
* @param list<AttributeGroup> $attrGroups PHP attribute groups
*/
public function __construct(
$var, ?Expr $default = null, $type = null,
$var, ?Expr $default = null, ?Node $type = null,
bool $byRef = false, bool $variadic = false,
array $attributes = [],
int $flags = 0,
array $attrGroups = []
) {
$this->attributes = $attributes;
$this->type = \is_string($type) ? new Identifier($type) : $type;
$this->type = $type;
$this->byRef = $byRef;
$this->variadic = $variadic;
$this->var = $var;

@@ -10,31 +10,36 @@ class ClassConst extends Node\Stmt {
public $flags;
/** @var Node\Const_[] Constant declarations */
public $consts;
/** @var Node\AttributeGroup[] */
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/** @var Node\Identifier|Node\Name|Node\ComplexType|null Type declaration */
public $type;
/**
* Constructs a class const list node.
*
* @param Node\Const_[] $consts Constant declarations
* @param int $flags Modifiers
* @param Node\Const_[] $consts Constant declarations
* @param int $flags Modifiers
* @param array<string, mixed> $attributes Additional attributes
* @param list<Node\AttributeGroup> $attrGroups PHP attribute groups
* @param null|Node\Identifier|Node\Name|Node\ComplexType $type Type declaration
*/
public function __construct(
array $consts,
int $flags = 0,
array $attributes = [],
array $attrGroups = []
array $attrGroups = [],
?Node $type = null
) {
$this->attributes = $attributes;
$this->flags = $flags;
$this->consts = $consts;
$this->attrGroups = $attrGroups;
$this->type = $type;
}
public function getSubNodeNames(): array {
return ['attrGroups', 'flags', 'consts'];
return ['attrGroups', 'flags', 'type', 'consts'];
}
/**

@@ -51,7 +51,7 @@ class ClassMethod extends Node\Stmt implements FunctionLike {
* flags?: int,
* byRef?: bool,
* params?: Node\Param[],
* returnType?: null|string|Node\Identifier|Node\Name|Node\ComplexType,
* returnType?: null|Node\Identifier|Node\Name|Node\ComplexType,
* stmts?: Node\Stmt[]|null,
* attrGroups?: Node\AttributeGroup[],
* } $subNodes Array of the following optional subnodes:
@@ -69,8 +69,7 @@ class ClassMethod extends Node\Stmt implements FunctionLike {
$this->byRef = $subNodes['byRef'] ?? false;
$this->name = \is_string($name) ? new Node\Identifier($name) : $name;
$this->params = $subNodes['params'] ?? [];
$returnType = $subNodes['returnType'] ?? null;
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
$this->returnType = $subNodes['returnType'] ?? null;
$this->stmts = array_key_exists('stmts', $subNodes) ? $subNodes['stmts'] : [];
$this->attrGroups = $subNodes['attrGroups'] ?? [];
}

@@ -2,7 +2,6 @@
namespace PhpParser\Node\Stmt;
use PhpParser\Error;
use PhpParser\Modifiers;
use PhpParser\Node;

@@ -29,7 +29,7 @@ class Function_ extends Node\Stmt implements FunctionLike {
* @param array{
* byRef?: bool,
* params?: Node\Param[],
* returnType?: null|string|Node\Identifier|Node\Name|Node\ComplexType,
* returnType?: null|Node\Identifier|Node\Name|Node\ComplexType,
* stmts?: Node\Stmt[],
* attrGroups?: Node\AttributeGroup[],
* } $subNodes Array of the following optional subnodes:
@@ -45,8 +45,7 @@ class Function_ extends Node\Stmt implements FunctionLike {
$this->byRef = $subNodes['byRef'] ?? false;
$this->name = \is_string($name) ? new Node\Identifier($name) : $name;
$this->params = $subNodes['params'] ?? [];
$returnType = $subNodes['returnType'] ?? null;
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
$this->returnType = $subNodes['returnType'] ?? null;
$this->stmts = $subNodes['stmts'] ?? [];
$this->attrGroups = $subNodes['attrGroups'] ?? [];
}

@@ -25,14 +25,14 @@ class Property extends Node\Stmt {
* @param int $flags Modifiers
* @param PropertyItem[] $props Properties
* @param array<string, mixed> $attributes Additional attributes
* @param null|string|Identifier|Name|ComplexType $type Type declaration
* @param null|Identifier|Name|ComplexType $type Type declaration
* @param Node\AttributeGroup[] $attrGroups PHP attribute groups
*/
public function __construct(int $flags, array $props, array $attributes = [], $type = null, array $attrGroups = []) {
public function __construct(int $flags, array $props, array $attributes = [], ?Node $type = null, array $attrGroups = []) {
$this->attributes = $attributes;
$this->flags = $flags;
$this->props = $props;
$this->type = \is_string($type) ? new Identifier($type) : $type;
$this->type = $type;
$this->attrGroups = $attrGroups;
}

@@ -3,7 +3,6 @@
namespace PhpParser\Node;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Stmt\Use_;
class UseItem extends Node\Stmt {

@@ -3,7 +3,6 @@
namespace PhpParser;
use PhpParser\Node\Expr\Include_;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\GroupUse;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\UseItem;

@@ -4,39 +4,24 @@ namespace PhpParser;
class NodeTraverser implements NodeTraverserInterface {
/**
* If NodeVisitor::enterNode() returns DONT_TRAVERSE_CHILDREN, child nodes
* of the current node will not be traversed for any visitors.
*
* For subsequent visitors enterNode() will still be called on the current
* node and leaveNode() will also be invoked for the current node.
* @deprecated Use NodeVisitor::DONT_TRAVERSE_CHILDREN instead.
*/
public const DONT_TRAVERSE_CHILDREN = 1;
public const DONT_TRAVERSE_CHILDREN = NodeVisitor::DONT_TRAVERSE_CHILDREN;
/**
* If NodeVisitor::enterNode() or NodeVisitor::leaveNode() returns
* STOP_TRAVERSAL, traversal is aborted.
*
* The afterTraverse() method will still be invoked.
* @deprecated Use NodeVisitor::STOP_TRAVERSAL instead.
*/
public const STOP_TRAVERSAL = 2;
public const STOP_TRAVERSAL = NodeVisitor::STOP_TRAVERSAL;
/**
* If NodeVisitor::leaveNode() returns REMOVE_NODE for a node that occurs
* in an array, it will be removed from the array.
*
* For subsequent visitors leaveNode() will still be invoked for the
* removed node.
* @deprecated Use NodeVisitor::REMOVE_NODE instead.
*/
public const REMOVE_NODE = 3;
public const REMOVE_NODE = NodeVisitor::REMOVE_NODE;
/**
* If NodeVisitor::enterNode() returns DONT_TRAVERSE_CURRENT_AND_CHILDREN, child nodes
* of the current node will not be traversed for any visitors.
*
* For subsequent visitors enterNode() will not be called as well.
* leaveNode() will be invoked for visitors that has enterNode() method invoked.
* @deprecated Use NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN instead.
*/
public const DONT_TRAVERSE_CURRENT_AND_CHILDREN = 4;
public const DONT_TRAVERSE_CURRENT_AND_CHILDREN = NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
/** @var list<NodeVisitor> Visitors */
protected $visitors = [];
@@ -63,11 +48,9 @@ class NodeTraverser implements NodeTraverserInterface {
* @param NodeVisitor $visitor
*/
public function removeVisitor(NodeVisitor $visitor): void {
foreach ($this->visitors as $index => $storedVisitor) {
if ($storedVisitor === $visitor) {
unset($this->visitors[$index]);
break;
}
$index = array_search($visitor, $this->visitors);
if ($index !== false) {
array_splice($this->visitors, $index, 1, []);
}
}
@@ -89,7 +72,8 @@ class NodeTraverser implements NodeTraverserInterface {
$nodes = $this->traverseArray($nodes);
foreach ($this->visitors as $visitor) {
for ($i = \count($this->visitors) - 1; $i >= 0; --$i) {
$visitor = $this->visitors[$i];
if (null !== $return = $visitor->afterTraverse($nodes)) {
$nodes = $return;
}
@@ -102,37 +86,37 @@ class NodeTraverser implements NodeTraverserInterface {
* Recursively traverse a node.
*
* @param Node $node Node to traverse.
*
* @return Node Result of traversal (may be original node or new one)
*/
protected function traverseNode(Node $node): Node {
protected function traverseNode(Node $node): void {
foreach ($node->getSubNodeNames() as $name) {
$subNode =& $node->$name;
$subNode = $node->$name;
if (\is_array($subNode)) {
$subNode = $this->traverseArray($subNode);
$node->$name = $this->traverseArray($subNode);
if ($this->stopTraversal) {
break;
}
} elseif ($subNode instanceof Node) {
$traverseChildren = true;
$breakVisitorIndex = null;
$visitorIndex = -1;
foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->enterNode($subNode);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($subNode, $return);
$subNode = $return;
} elseif (self::DONT_TRAVERSE_CHILDREN === $return) {
$subNode = $node->$name = $return;
} elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
} elseif (self::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
} elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
$traverseChildren = false;
$breakVisitorIndex = $visitorIndex;
break;
} elseif (self::STOP_TRAVERSAL === $return) {
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
$node->$name = null;
continue 2;
} else {
throw new \LogicException(
'enterNode() returned invalid value of type ' . gettype($return)
@@ -142,22 +126,26 @@ class NodeTraverser implements NodeTraverserInterface {
}
if ($traverseChildren) {
$subNode = $this->traverseNode($subNode);
$this->traverseNode($subNode);
if ($this->stopTraversal) {
break;
}
}
foreach ($this->visitors as $visitorIndex => $visitor) {
for (; $visitorIndex >= 0; --$visitorIndex) {
$visitor = $this->visitors[$visitorIndex];
$return = $visitor->leaveNode($subNode);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($subNode, $return);
$subNode = $return;
} elseif (self::STOP_TRAVERSAL === $return) {
$subNode = $node->$name = $return;
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
$node->$name = null;
break;
} elseif (\is_array($return)) {
throw new \LogicException(
'leaveNode() may only return an array ' .
@@ -169,15 +157,9 @@ class NodeTraverser implements NodeTraverserInterface {
);
}
}
if ($breakVisitorIndex === $visitorIndex) {
break;
}
}
}
}
return $node;
}
/**
@@ -193,7 +175,7 @@ class NodeTraverser implements NodeTraverserInterface {
foreach ($nodes as $i => &$node) {
if ($node instanceof Node) {
$traverseChildren = true;
$breakVisitorIndex = null;
$visitorIndex = -1;
foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->enterNode($node);
@@ -204,18 +186,20 @@ class NodeTraverser implements NodeTraverserInterface {
} elseif (\is_array($return)) {
$doNodes[] = [$i, $return];
continue 2;
} elseif (self::REMOVE_NODE === $return) {
} elseif (NodeVisitor::REMOVE_NODE === $return) {
$doNodes[] = [$i, []];
continue 2;
} elseif (self::DONT_TRAVERSE_CHILDREN === $return) {
} elseif (NodeVisitor::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
} elseif (self::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
} elseif (NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
$traverseChildren = false;
$breakVisitorIndex = $visitorIndex;
break;
} elseif (self::STOP_TRAVERSAL === $return) {
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
throw new \LogicException(
'REPLACE_WITH_NULL can not be used if the parent structure is an array');
} else {
throw new \LogicException(
'enterNode() returned invalid value of type ' . gettype($return)
@@ -225,13 +209,14 @@ class NodeTraverser implements NodeTraverserInterface {
}
if ($traverseChildren) {
$node = $this->traverseNode($node);
$this->traverseNode($node);
if ($this->stopTraversal) {
break;
}
}
foreach ($this->visitors as $visitorIndex => $visitor) {
for (; $visitorIndex >= 0; --$visitorIndex) {
$visitor = $this->visitors[$visitorIndex];
$return = $visitor->leaveNode($node);
if (null !== $return) {
@@ -241,22 +226,21 @@ class NodeTraverser implements NodeTraverserInterface {
} elseif (\is_array($return)) {
$doNodes[] = [$i, $return];
break;
} elseif (self::REMOVE_NODE === $return) {
} elseif (NodeVisitor::REMOVE_NODE === $return) {
$doNodes[] = [$i, []];
break;
} elseif (self::STOP_TRAVERSAL === $return) {
} elseif (NodeVisitor::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (NodeVisitor::REPLACE_WITH_NULL === $return) {
throw new \LogicException(
'REPLACE_WITH_NULL can not be used if the parent structure is an array');
} else {
throw new \LogicException(
'leaveNode() returned invalid value of type ' . gettype($return)
);
}
}
if ($breakVisitorIndex === $visitorIndex) {
break;
}
}
} elseif (\is_array($node)) {
throw new \LogicException('Invalid node structure: Contains nested arrays');

@@ -3,6 +3,48 @@
namespace PhpParser;
interface NodeVisitor {
/**
* If NodeVisitor::enterNode() returns DONT_TRAVERSE_CHILDREN, child nodes
* of the current node will not be traversed for any visitors.
*
* For subsequent visitors enterNode() will still be called on the current
* node and leaveNode() will also be invoked for the current node.
*/
public const DONT_TRAVERSE_CHILDREN = 1;
/**
* If NodeVisitor::enterNode() or NodeVisitor::leaveNode() returns
* STOP_TRAVERSAL, traversal is aborted.
*
* The afterTraverse() method will still be invoked.
*/
public const STOP_TRAVERSAL = 2;
/**
* If NodeVisitor::leaveNode() returns REMOVE_NODE for a node that occurs
* in an array, it will be removed from the array.
*
* For subsequent visitors leaveNode() will still be invoked for the
* removed node.
*/
public const REMOVE_NODE = 3;
/**
* If NodeVisitor::enterNode() returns DONT_TRAVERSE_CURRENT_AND_CHILDREN, child nodes
* of the current node will not be traversed for any visitors.
*
* For subsequent visitors enterNode() will not be called as well.
* leaveNode() will be invoked for visitors that has enterNode() method invoked.
*/
public const DONT_TRAVERSE_CURRENT_AND_CHILDREN = 4;
/**
* If NodeVisitor::enterNode() or NodeVisitor::leaveNode() returns REPLACE_WITH_NULL,
* the node will be replaced with null. This is not a legal return value if the node is part
* of an array, rather than another node.
*/
public const REPLACE_WITH_NULL = 5;
/**
* Called once before traversal.
*
@@ -24,14 +66,16 @@ interface NodeVisitor {
* => $node stays as-is
* * array (of Nodes)
* => The return value is merged into the parent array (at the position of the $node)
* * NodeTraverser::REMOVE_NODE
* * NodeVisitor::REMOVE_NODE
* => $node is removed from the parent array
* * NodeTraverser::DONT_TRAVERSE_CHILDREN
* * NodeVisitor::REPLACE_WITH_NULL
* => $node is replaced with null
* * NodeVisitor::DONT_TRAVERSE_CHILDREN
* => Children of $node are not traversed. $node stays as-is
* * NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN
* * NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN
* => Further visitors for the current node are skipped, and its children are not
* traversed. $node stays as-is.
* * NodeTraverser::STOP_TRAVERSAL
* * NodeVisitor::STOP_TRAVERSAL
* => Traversal is aborted. $node stays as-is
* * otherwise
* => $node is set to the return value
@@ -48,9 +92,11 @@ interface NodeVisitor {
* Return value semantics:
* * null
* => $node stays as-is
* * NodeTraverser::REMOVE_NODE
* * NodeVisitor::REMOVE_NODE
* => $node is removed from the parent array
* * NodeTraverser::STOP_TRAVERSAL
* * NodeVisitor::REPLACE_WITH_NULL
* => $node is replaced with null
* * NodeVisitor::STOP_TRAVERSAL
* => Traversal is aborted. $node stays as-is
* * array (of Nodes)
* => The return value is merged into the parent array (at the position of the $node)

@@ -3,7 +3,7 @@
namespace PhpParser\NodeVisitor;
use PhpParser\Node;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor;
use PhpParser\NodeVisitorAbstract;
/**
@@ -41,7 +41,7 @@ class FirstFindingVisitor extends NodeVisitorAbstract {
$filterCallback = $this->filterCallback;
if ($filterCallback($node)) {
$this->foundNode = $node;
return NodeTraverser::STOP_TRAVERSAL;
return NodeVisitor::STOP_TRAVERSAL;
}
return null;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@@ -141,6 +141,13 @@ class PhpVersion {
return $this->id < 70000;
}
/**
* Whether this version allows DEL (\x7f) to occur in identifiers.
*/
public function allowsDelInIdentifiers(): bool {
return $this->id < 70100;
}
/**
* Whether this version support yield in expression context without parentheses.
*/

@@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
namespace PhpParser;

@@ -80,15 +80,15 @@ class Standard extends PrettyPrinterAbstract {
// Names
protected function pName(Name $node): string {
return implode('\\', $node->parts);
return $node->name;
}
protected function pName_FullyQualified(Name\FullyQualified $node): string {
return '\\' . implode('\\', $node->parts);
return '\\' . $node->name;
}
protected function pName_Relative(Name\Relative $node): string {
return 'namespace\\' . implode('\\', $node->parts);
return 'namespace\\' . $node->name;
}
// Magic Constants
@@ -134,12 +134,12 @@ class Standard extends PrettyPrinterAbstract {
$label = $node->getAttribute('docLabel');
if ($label && !$this->containsEndLabel($node->value, $label)) {
if ($node->value === '') {
return "<<<'$label'\n$label" . $this->docStringEndToken;
return "<<<'$label'{$this->newline}$label{$this->docStringEndToken}";
}
// Make sure trailing \r is not combined with following \n into CRLF.
if ($node->value[strlen($node->value) - 1] !== "\r") {
return "<<<'$label'\n$node->value\n$label"
return "<<<'$label'{$this->newline}{$node->value}{$this->newline}$label"
. $this->docStringEndToken;
}
}
@@ -152,10 +152,10 @@ class Standard extends PrettyPrinterAbstract {
$escaped = $this->escapeString($node->value, null);
if ($label && !$this->containsEndLabel($escaped, $label)) {
if ($escaped === '') {
return "<<<$label\n$label" . $this->docStringEndToken;
return "<<<$label{$this->newline}$label{$this->docStringEndToken}";
}
return "<<<$label\n" . $escaped . "\n$label"
return "<<<$label{$this->newline}$escaped{$this->newline}$label"
. $this->docStringEndToken;
}
/* break missing intentionally */
@@ -174,11 +174,11 @@ class Standard extends PrettyPrinterAbstract {
&& $node->parts[0] instanceof Node\InterpolatedStringPart
&& $node->parts[0]->value === ''
) {
return "<<<$label\n$label" . $this->docStringEndToken;
return "<<<$label{$this->newline}$label{$this->docStringEndToken}";
}
return "<<<$label\n" . $this->pEncapsList($node->parts, null) . "\n$label"
. $this->docStringEndToken;
return "<<<$label{$this->newline}" . $this->pEncapsList($node->parts, null)
. "{$this->newline}$label{$this->docStringEndToken}";
}
}
return '"' . $this->pEncapsList($node->parts, '"') . '"';
@@ -728,7 +728,7 @@ class Standard extends PrettyPrinterAbstract {
}
return $this->pPrefixOp(
Expr\Yield_::class, 'yield ' . $this->pKey($node->key),
$node->value, $precedence, $lhsPrecedence);
$node->value, $precedence, $lhsPrecedence);
}
}
@@ -842,7 +842,9 @@ class Standard extends PrettyPrinterAbstract {
protected function pStmt_ClassConst(Stmt\ClassConst $node): string {
return $this->pAttrGroups($node->attrGroups)
. $this->pModifiers($node->flags)
. 'const ' . $this->pCommaSeparated($node->consts) . ';';
. 'const '
. (null !== $node->type ? $this->p($node->type) . ' ' : '')
. $this->pCommaSeparated($node->consts) . ';';
}
protected function pStmt_Function(Stmt\Function_ $node): string {
@@ -881,6 +883,10 @@ class Standard extends PrettyPrinterAbstract {
}
protected function pStmt_Else(Stmt\Else_ $node): string {
if (\count($node->stmts) === 1 && $node->stmts[0] instanceof Stmt\If_) {
// Print as "else if" rather than "else { if }"
return 'else ' . $this->p($node->stmts[0]);
}
return 'else {' . $this->pStmts($node->stmts) . $this->nl . '}';
}
@@ -987,7 +993,7 @@ class Standard extends PrettyPrinterAbstract {
}
protected function pStmt_InlineHTML(Stmt\InlineHTML $node): string {
$newline = $node->getAttribute('hasLeadingNewline', true) ? "\n" : '';
$newline = $node->getAttribute('hasLeadingNewline', true) ? $this->newline : '';
return '?>' . $newline . $node->value . '<?php ';
}

@@ -104,6 +104,8 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
/** @var int Current indentation level. */
protected $indentLevel;
/** @var string Newline style. Does not include current indentation. */
protected $newline;
/** @var string Newline including current indentation. */
protected $nl;
/** @var string|null Token placed at end of doc string to ensure it is followed by a newline.
@@ -159,20 +161,29 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
* Creates a pretty printer instance using the given options.
*
* Supported options:
* * PhpVersion $phpVersion: The PHP version to target (default to PHP 7.0). This option
* * PhpVersion $phpVersion: The PHP version to target (default to PHP 7.1). This option
* controls compatibility of the generated code with older PHP
* versions in cases where a simple stylistic choice exists (e.g.
* array() vs []). It is safe to pretty-print an AST for a newer
* PHP version while specifying an older target (but the result will
* of course not be compatible with the older version in that case).
* * string $newline: The newline style to use. Should be "\n" (default) or "\r\n".
* * bool $shortArraySyntax: Whether to use [] instead of array() as the default array
* syntax, if the node does not specify a format. Defaults to whether
* the phpVersion support short array syntax.
*
* @param array{phpVersion?: PhpVersion, shortArraySyntax?: bool} $options Dictionary of formatting options
* @param array{
* phpVersion?: PhpVersion, newline?: string, shortArraySyntax?: bool
* } $options Dictionary of formatting options
*/
public function __construct(array $options = []) {
$this->phpVersion = $options['phpVersion'] ?? PhpVersion::fromComponents(7, 0);
$this->phpVersion = $options['phpVersion'] ?? PhpVersion::fromComponents(7, 1);
$this->newline = $options['newline'] ?? "\n";
if ($this->newline !== "\n" && $this->newline != "\r\n") {
throw new \LogicException('Option "newline" must be one of "\n" or "\r\n"');
}
$this->shortArraySyntax =
$options['shortArraySyntax'] ?? $this->phpVersion->supportsShortArraySyntax();
$this->docStringEndToken =
@@ -184,7 +195,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
*/
protected function resetState(): void {
$this->indentLevel = 0;
$this->nl = "\n";
$this->nl = $this->newline;
$this->origTokens = null;
}
@@ -195,7 +206,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
*/
protected function setIndentLevel(int $level): void {
$this->indentLevel = $level;
$this->nl = "\n" . \str_repeat(' ', $level);
$this->nl = $this->newline . \str_repeat(' ', $level);
}
/**
@@ -212,7 +223,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
protected function outdent(): void {
assert($this->indentLevel >= 4);
$this->indentLevel -= 4;
$this->nl = "\n" . str_repeat(' ', $this->indentLevel);
$this->nl = $this->newline . str_repeat(' ', $this->indentLevel);
}
/**
@@ -250,13 +261,13 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
*/
public function prettyPrintFile(array $stmts): string {
if (!$stmts) {
return "<?php\n\n";
return "<?php" . $this->newline . $this->newline;
}
$p = "<?php\n\n" . $this->prettyPrint($stmts);
$p = "<?php" . $this->newline . $this->newline . $this->prettyPrint($stmts);
if ($stmts[0] instanceof Stmt\InlineHTML) {
$p = preg_replace('/^<\?php\s+\?>\n?/', '', $p);
$p = preg_replace('/^<\?php\s+\?>\r?\n?/', '', $p);
}
if ($stmts[count($stmts) - 1] instanceof Stmt\InlineHTML) {
$p = preg_replace('/<\?php$/', '', rtrim($p));
@@ -290,8 +301,11 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
protected function handleMagicTokens(string $str): string {
if ($this->docStringEndToken !== null) {
// Replace doc-string-end tokens with nothing or a newline
$str = str_replace($this->docStringEndToken . ";\n", ";\n", $str);
$str = str_replace($this->docStringEndToken, "\n", $str);
$str = str_replace(
$this->docStringEndToken . ';' . $this->newline,
';' . $this->newline,
$str);
$str = str_replace($this->docStringEndToken, $this->newline, $str);
}
return $str;
@@ -537,10 +551,10 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
} else {
// Fallback
// TODO Add <?php properly
$result = "<?php\n" . $this->pStmts($stmts, false);
$result = "<?php" . $this->newline . $this->pStmts($stmts, false);
}
return ltrim($this->handleMagicTokens($result));
return $this->handleMagicTokens($result);
}
protected function pFallback(Node $node, int $precedence, int $lhsPrecedence): string {
@@ -1244,10 +1258,12 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
$this->labelCharMap = [];
for ($i = 0; $i < 256; $i++) {
// Since PHP 7.1 The lower range is 0x80. However, we also want to support code for
// older versions.
$chr = chr($i);
$this->labelCharMap[$chr] = $i >= 0x7f || ctype_alnum($chr);
$this->labelCharMap[$chr] = $i >= 0x80 || ctype_alnum($chr);
}
if ($this->phpVersion->allowsDelInIdentifiers()) {
$this->labelCharMap["\x7f"] = true;
}
}
@@ -1389,6 +1405,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
'Param->default' => $stripEquals,
'Stmt_Break->num' => $stripBoth,
'Stmt_Catch->var' => $stripLeft,
'Stmt_ClassConst->type' => $stripRight,
'Stmt_ClassMethod->returnType' => $stripColon,
'Stmt_Class->extends' => ['left' => \T_EXTENDS],
'Stmt_Enum->scalarType' => $stripColon,
@@ -1432,6 +1449,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
'Stmt_Break->num' => [\T_BREAK, false, ' ', null],
'Stmt_Catch->var' => [null, false, ' ', null],
'Stmt_ClassMethod->returnType' => [')', false, ': ', null],
'Stmt_ClassConst->type' => [\T_CONST, false, ' ', null],
'Stmt_Class->extends' => [null, false, ' extends ', null],
'Stmt_Enum->scalarType' => [null, false, ' : ', null],
'Stmt_EnumCase->expr' => [null, false, ' = ', null],
@@ -1629,6 +1647,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
Stmt\ClassMethod::class . '->flags' => ['pModifiers', \T_FUNCTION],
Stmt\Class_::class . '->flags' => ['pModifiers', \T_CLASS],
Stmt\Property::class . '->flags' => ['pModifiers', \T_VARIABLE],
PrintableNewAnonClassNode::class . '->flags' => ['pModifiers', \T_CLASS],
Param::class . '->flags' => ['pModifiers', \T_VARIABLE],
Expr\Closure::class . '->static' => ['pStatic', \T_FUNCTION],
Expr\ArrowFunction::class . '->static' => ['pStatic', \T_FN],

@@ -142,6 +142,18 @@ class ClassConstTest extends \PHPUnit\Framework\TestCase {
);
}
public function testType() {
$node = $this->createClassConstBuilder('TYPE', 1)
->setType('int')
->getNode();
$this->assertEquals(
new Stmt\ClassConst(
[new Const_('TYPE', new Int_(1))],
0, [], [], new Identifier('int')),
$node
);
}
/**
* @dataProvider provideTestDefaultValues
*/

@@ -2,6 +2,7 @@
namespace PhpParser\Builder;
use PhpParser\Modifiers;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Attribute;
@@ -204,6 +205,54 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
);
}
public function testMakePublic() {
$node = $this->createParamBuilder('test')
->makePublic()
->getNode()
;
$this->assertEquals(
new Node\Param(new Expr\Variable('test'), null, null, false, false, [], Modifiers::PUBLIC),
$node
);
}
public function testMakeProtected() {
$node = $this->createParamBuilder('test')
->makeProtected()
->getNode()
;
$this->assertEquals(
new Node\Param(new Expr\Variable('test'), null, null, false, false, [], Modifiers::PROTECTED),
$node
);
}
public function testMakePrivate() {
$node = $this->createParamBuilder('test')
->makePrivate()
->getNode()
;
$this->assertEquals(
new Node\Param(new Expr\Variable('test'), null, null, false, false, [], Modifiers::PRIVATE),
$node
);
}
public function testMakeReadonly() {
$node = $this->createParamBuilder('test')
->makeReadonly()
->getNode()
;
$this->assertEquals(
new Node\Param(new Expr\Variable('test'), null, null, false, false, [], Modifiers::READONLY),
$node
);
}
public function testAddAttribute() {
$attribute = new Attribute(
new Name('Attr'),

@@ -3,6 +3,7 @@
namespace PhpParser;
use PhpParser\Builder\Class_;
use PhpParser\Node\Identifier;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;
use PhpParser\Node\Expr;
@@ -136,7 +137,7 @@ class BuilderHelpersTest extends \PHPUnit\Framework\TestCase {
$intName = new Node\Name('int');
$this->assertSame($intName, BuilderHelpers::normalizeType($intName));
$intNullable = new Node\NullableType('int');
$intNullable = new Node\NullableType(new Identifier('int'));
$this->assertSame($intNullable, BuilderHelpers::normalizeType($intNullable));
$unionType = new Node\UnionType([new Node\Identifier('int'), new Node\Identifier('string')]);

@@ -47,7 +47,7 @@ class CodeTestParser {
}
$result .= $lastPart;
}
return $result;
return $result . "\n";
}
private function extractMode(string $expected): array {

@@ -33,47 +33,37 @@ class CommentTest extends \PHPUnit\Framework\TestCase {
['// Some text', '// Some text'],
['/* Some text */', '/* Some text */'],
[
'/**
* Some text.
* Some more text.
*/',
'/**
* Some text.
* Some more text.
*/'
"/**\n * Some text.\n * Some more text.\n */",
"/**\n * Some text.\n * Some more text.\n */"
],
[
'/*
Some text.
Some more text.
*/',
'/*
Some text.
Some more text.
*/'
"/**\r\n * Some text.\r\n * Some more text.\r\n */",
"/**\n * Some text.\n * Some more text.\n */"
],
[
'/* Some text.
More text.
Even more text. */',
'/* Some text.
More text.
Even more text. */'
"/*\n Some text.\n Some more text.\n */",
"/*\n Some text.\n Some more text.\n*/"
],
[
'/* Some text.
More text.
Indented text. */',
'/* Some text.
More text.
Indented text. */',
"/*\r\n Some text.\r\n Some more text.\r\n */",
"/*\n Some text.\n Some more text.\n*/"
],
[
"/* Some text.\n More text.\n Even more text. */",
"/* Some text.\n More text.\n Even more text. */"
],
[
"/* Some text.\r\n More text.\r\n Even more text. */",
"/* Some text.\n More text.\n Even more text. */"
],
[
"/* Some text.\n More text.\n Indented text. */",
"/* Some text.\n More text.\n Indented text. */",
],
// invalid comment -> no reformatting
[
'hallo
world',
'hallo
world',
"hello\n world",
"hello\n world",
],
];
}

@@ -65,4 +65,15 @@ class DifferTest extends \PHPUnit\Framework\TestCase {
['abcde', 'axyzue', 'a-b-c-d+x+y+z+ue'],
];
}
public function testNonContiguousIndices() {
$differ = new Differ(function ($a, $b) {
return $a === $b;
});
$diff = $differ->diff([0 => 'a', 2 => 'b'], [0 => 'a', 3 => 'b']);
$this->assertEquals([
new DiffElem(DiffElem::TYPE_KEEP, 'a', 'a'),
new DiffElem(DiffElem::TYPE_KEEP, 'b', 'b'),
], $diff);
}
}

@@ -47,6 +47,14 @@ class LexerTest extends \PHPUnit\Framework\TestCase {
];
}
public function testDefaultErrorHandler() {
$this->expectException(Error::class);
$this->expectExceptionMessage('Unterminated comment on line 1');
$lexer = $this->getLexer();
$lexer->startLexing("<?php readonly /*");
$lexer->getNextToken();
}
/**
* @dataProvider provideTestLex
*/

@@ -5,23 +5,25 @@ namespace PhpParser\Node;
class NameTest extends \PHPUnit\Framework\TestCase {
public function testConstruct() {
$name = new Name(['foo', 'bar']);
$this->assertSame(['foo', 'bar'], $name->parts);
$this->assertSame('foo\bar', $name->name);
$name = new Name('foo\bar');
$this->assertSame(['foo', 'bar'], $name->parts);
$this->assertSame('foo\bar', $name->name);
$name = new Name($name);
$this->assertSame(['foo', 'bar'], $name->parts);
$this->assertSame('foo\bar', $name->name);
}
public function testGet() {
$name = new Name('foo');
$this->assertSame('foo', $name->getFirst());
$this->assertSame('foo', $name->getLast());
$this->assertSame(['foo'], $name->getParts());
$name = new Name('foo\bar');
$this->assertSame('foo', $name->getFirst());
$this->assertSame('bar', $name->getLast());
$this->assertSame(['foo', 'bar'], $name->getParts());
}
public function testToString() {

@@ -34,10 +34,7 @@ class NodeDumperTest extends \PHPUnit\Framework\TestCase {
[
new Node\Name(['Hallo', 'World']),
'Name(
parts: array(
0: Hallo
1: World
)
name: Hallo\World
)'
],
[

@@ -3,7 +3,10 @@
namespace PhpParser;
use PhpParser\Node\Expr;
use PhpParser\Node\Scalar\Int_;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Else_;
use PhpParser\Node\Stmt\If_;
class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
public function testNonModifying() {
@@ -34,8 +37,9 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
$str2Node = new String_('Bar');
$printNode = new Expr\Print_($str1Node);
// first visitor changes the node, second verifies the change
$visitor1 = new NodeVisitorForTesting([
// Visitor 2 performs changes, visitors 1 and 3 observe the changes.
$visitor1 = new NodeVisitorForTesting();
$visitor2 = new NodeVisitorForTesting([
['beforeTraverse', [], [$str1Node]],
['enterNode', $str1Node, $printNode],
['enterNode', $str1Node, $str2Node],
@@ -43,22 +47,35 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
['leaveNode', $printNode, $str1Node],
['afterTraverse', [$str1Node], []],
]);
$visitor2 = new NodeVisitorForTesting();
$visitor3 = new NodeVisitorForTesting();
$traverser = new NodeTraverser();
$traverser->addVisitor($visitor1);
$traverser->addVisitor($visitor2);
$traverser->addVisitor($visitor3);
// as all operations are reversed we end where we start
$this->assertEquals([], $traverser->traverse([]));
$this->assertEquals([
['beforeTraverse', [$str1Node]],
['enterNode', $printNode],
['enterNode', $str2Node],
// Sees nodes before changes on entry.
['beforeTraverse', []],
['enterNode', $str1Node],
['enterNode', $str1Node],
// Sees nodes after changes on leave.
['leaveNode', $str1Node],
['leaveNode', $str1Node],
['afterTraverse', []],
], $visitor2->trace);
], $visitor1->trace);
$this->assertEquals([
// Sees nodes after changes on entry.
['beforeTraverse', [$str1Node]],
['enterNode', $printNode],
['enterNode', $str2Node],
// Sees nodes before changes on leave.
['leaveNode', $str2Node],
['leaveNode', $printNode],
['afterTraverse', [$str1Node]],
], $visitor3->trace);
}
public function testRemoveFromLeave() {
@@ -66,13 +83,13 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
$str2Node = new String_('Bar');
$visitor = new NodeVisitorForTesting([
['leaveNode', $str1Node, NodeTraverser::REMOVE_NODE],
['leaveNode', $str1Node, NodeVisitor::REMOVE_NODE],
]);
$visitor2 = new NodeVisitorForTesting();
$traverser = new NodeTraverser();
$traverser->addVisitor($visitor);
$traverser->addVisitor($visitor2);
$traverser->addVisitor($visitor);
$stmts = [$str1Node, $str2Node];
$this->assertEquals([$str2Node], $traverser->traverse($stmts));
@@ -90,7 +107,7 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
$str2Node = new String_('Bar');
$visitor = new NodeVisitorForTesting([
['enterNode', $str1Node, NodeTraverser::REMOVE_NODE],
['enterNode', $str1Node, NodeVisitor::REMOVE_NODE],
]);
$visitor2 = new NodeVisitorForTesting();
@@ -172,10 +189,10 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
$stmts = [$printNode, $negNode];
$visitor1 = new NodeVisitorForTesting([
['enterNode', $printNode, NodeTraverser::DONT_TRAVERSE_CHILDREN],
['enterNode', $printNode, NodeVisitor::DONT_TRAVERSE_CHILDREN],
]);
$visitor2 = new NodeVisitorForTesting([
['enterNode', $mulNode, NodeTraverser::DONT_TRAVERSE_CHILDREN],
['enterNode', $mulNode, NodeVisitor::DONT_TRAVERSE_CHILDREN],
]);
$expectedTrace = [
@@ -209,8 +226,8 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
$stmts = [$printNode, $negNode];
$visitor1 = new NodeVisitorForTesting([
['enterNode', $printNode, NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN],
['enterNode', $mulNode, NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN],
['enterNode', $printNode, NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN],
['enterNode', $mulNode, NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN],
['leaveNode', $mulNode, $divNode],
]);
$visitor2 = new NodeVisitorForTesting();
@@ -250,7 +267,7 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
// From enterNode() with array parent
$visitor = new NodeVisitorForTesting([
['enterNode', $mulNode, NodeTraverser::STOP_TRAVERSAL],
['enterNode', $mulNode, NodeVisitor::STOP_TRAVERSAL],
]);
$traverser = new NodeTraverser();
$traverser->addVisitor($visitor);
@@ -263,7 +280,7 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
// From enterNode with Node parent
$visitor = new NodeVisitorForTesting([
['enterNode', $varNode1, NodeTraverser::STOP_TRAVERSAL],
['enterNode', $varNode1, NodeVisitor::STOP_TRAVERSAL],
]);
$traverser = new NodeTraverser();
$traverser->addVisitor($visitor);
@@ -277,7 +294,7 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
// From leaveNode with Node parent
$visitor = new NodeVisitorForTesting([
['leaveNode', $varNode1, NodeTraverser::STOP_TRAVERSAL],
['leaveNode', $varNode1, NodeVisitor::STOP_TRAVERSAL],
]);
$traverser = new NodeTraverser();
$traverser->addVisitor($visitor);
@@ -292,7 +309,7 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
// From leaveNode with array parent
$visitor = new NodeVisitorForTesting([
['leaveNode', $mulNode, NodeTraverser::STOP_TRAVERSAL],
['leaveNode', $mulNode, NodeVisitor::STOP_TRAVERSAL],
]);
$traverser = new NodeTraverser();
$traverser->addVisitor($visitor);
@@ -310,8 +327,8 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
// Check that pending array modifications are still carried out
$visitor = new NodeVisitorForTesting([
['leaveNode', $mulNode, NodeTraverser::REMOVE_NODE],
['enterNode', $printNode, NodeTraverser::STOP_TRAVERSAL],
['leaveNode', $mulNode, NodeVisitor::REMOVE_NODE],
['enterNode', $printNode, NodeVisitor::STOP_TRAVERSAL],
]);
$traverser = new NodeTraverser();
$traverser->addVisitor($visitor);
@@ -329,6 +346,44 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
], $visitor->trace);
}
public function testReplaceWithNull() {
$one = new Int_(1);
$else1 = new Else_();
$else2 = new Else_();
$if1 = new If_($one, ['else' => $else1]);
$if2 = new If_($one, ['else' => $else2]);
$stmts = [$if1, $if2];
$visitor1 = new NodeVisitorForTesting([
['enterNode', $else1, NodeVisitor::REPLACE_WITH_NULL],
['leaveNode', $else2, NodeVisitor::REPLACE_WITH_NULL],
]);
$visitor2 = new NodeVisitorForTesting();
$traverser = new NodeTraverser();
$traverser->addVisitor($visitor1);
$traverser->addVisitor($visitor2);
$newStmts = $traverser->traverse($stmts);
$this->assertEquals([
new If_($one),
new If_($one),
], $newStmts);
$this->assertEquals([
['beforeTraverse', $stmts],
['enterNode', $if1],
['enterNode', $one],
// We never see the if1 Else node.
['leaveNode', $one],
['leaveNode', $if1],
['enterNode', $if2],
['enterNode', $one],
['leaveNode', $one],
// We do see the if2 Else node, as it will only be replaced afterwards.
['enterNode', $else2],
['leaveNode', $else2],
['leaveNode', $if2],
['afterTraverse', $stmts],
], $visitor2->trace);
}
public function testRemovingVisitor() {
$visitor1 = new class () extends NodeVisitorAbstract {};
$visitor2 = new class () extends NodeVisitorAbstract {};
@@ -348,7 +403,7 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
$traverser->removeVisitor($visitor2);
$postExpected = [0 => $visitor1, 2 => $visitor3];
$postExpected = [$visitor1, $visitor3];
$this->assertSame($postExpected, $getVisitors());
}
@@ -401,6 +456,12 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
$visitor8 = new NodeVisitorForTesting([
['enterNode', $num, new Node\Stmt\Return_()],
]);
$visitor9 = new NodeVisitorForTesting([
['enterNode', $expr, NodeVisitor::REPLACE_WITH_NULL],
]);
$visitor10 = new NodeVisitorForTesting([
['leaveNode', $expr, NodeVisitor::REPLACE_WITH_NULL],
]);
return [
[$stmts, $visitor1, 'enterNode() returned invalid value of type string'],
@@ -411,6 +472,8 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase {
[$stmts, $visitor6, 'leaveNode() returned invalid value of type bool'],
[$stmts, $visitor7, 'Trying to replace statement (Stmt_Expression) with expression (Scalar_Int). Are you missing a Stmt_Expression wrapper?'],
[$stmts, $visitor8, 'Trying to replace expression (Scalar_Int) with statement (Stmt_Return)'],
[$stmts, $visitor9, 'REPLACE_WITH_NULL can not be used if the parent structure is an array'],
[$stmts, $visitor10, 'REPLACE_WITH_NULL can not be used if the parent structure is an array'],
];
}
}

@@ -460,7 +460,7 @@ EOC;
$stmt = $stmts[0];
$assign = $stmt->stmts[1]->expr;
$this->assertSame(['Bar', 'Baz'], $assign->expr->class->parts);
$this->assertSame('Bar\\Baz', $assign->expr->class->name);
}
public function testSpecialClassNamesAreCaseInsensitive() {

@@ -210,6 +210,7 @@ use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;
use PhpParser\Modifiers;
\$fn = function(&\$stmts) { $modification };
CODE
);
@@ -269,4 +270,49 @@ CODE
$this->getTests(__DIR__ . '/../code/parser', 'test')
);
}
public function testWindowsNewline() {
$prettyPrinter = new Standard(['newline' => "\r\n"]);
$stmts = [
new Stmt\If_(new Int_(1), [
'stmts' => [
new Stmt\Echo_([new String_('Hello')]),
new Stmt\Echo_([new String_('World')]),
],
]),
];
$code = $prettyPrinter->prettyPrint($stmts);
$this->assertSame("if (1) {\r\n echo 'Hello';\r\n echo 'World';\r\n}", $code);
$code = $prettyPrinter->prettyPrintFile($stmts);
$this->assertSame("<?php\r\n\r\nif (1) {\r\n echo 'Hello';\r\n echo 'World';\r\n}", $code);
$stmts = [new Stmt\InlineHTML('Hello world')];
$code = $prettyPrinter->prettyPrintFile($stmts);
$this->assertSame("Hello world", $code);
$stmts = [
new Stmt\Expression(new String_('Test', [
'kind' => String_::KIND_NOWDOC,
'docLabel' => 'STR'
])),
new Stmt\Expression(new String_('Test 2', [
'kind' => String_::KIND_HEREDOC,
'docLabel' => 'STR'
])),
new Stmt\Expression(new InterpolatedString([new InterpolatedStringPart('Test 3')], [
'kind' => String_::KIND_HEREDOC,
'docLabel' => 'STR'
])),
];
$code = $prettyPrinter->prettyPrint($stmts);
$this->assertSame(
"<<<'STR'\r\nTest\r\nSTR;\r\n<<<STR\r\nTest 2\r\nSTR;\r\n<<<STR\r\nTest 3\r\nSTR\r\n;",
$code);
}
public function testInvalidNewline() {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Option "newline" must be one of "\n" or "\r\n"');
new PrettyPrinter\Standard(['newline' => 'foo']);
}
}

@@ -0,0 +1,9 @@
DEL after identifier
-----
<?php
"$a@@{ "\x7f" }@@";
-----
/* do nothing */
-----
<?php
"$a@@{ "\x7f" }@@";

@@ -110,3 +110,13 @@ function test()
foo();
baz();
}
-----
<?php $x;
-----
/* Do nothing, but make sure leading newlines are preserved. */
-----
<?php $x;

@@ -49,6 +49,10 @@ X
private
$x
;
const
X
= 1;
}
foreach (
@@ -86,6 +90,7 @@ $stmts[9]->expr = new Expr\Variable('x');
$stmts[10]->extends = new Node\Name\FullyQualified('Bar');
$stmts[10]->stmts[0]->returnType = new Node\Name('Y');
$stmts[10]->stmts[1]->props[0]->default = new Scalar\DNumber(42.0);
$stmts[10]->stmts[2]->type = new Node\Identifier('int');
$stmts[11]->keyVar = new Expr\Variable('z');
$stmts[12]->vars[0]->default = new Scalar\String_('abc');
$stmts[13]->finally = new Stmt\Finally_([]);
@@ -140,6 +145,10 @@ X extends \Bar
private
$x = 42.0
;
const int
X
= 1;
}
foreach (

@@ -141,7 +141,7 @@ function test() {
namespace
Foo;
-----
$stmts[0]->name->parts[0] = 'Xyz';
$stmts[0]->name->name = 'Xyz';
-----
<?php
namespace

@@ -207,4 +207,16 @@ class Foo
function getBaz()
{
}
}
}
-----
<?php
class Test {
use A, B;
}
-----
unset($stmts[0]->stmts[0]->traits[0]);
-----
<?php
class Test {
use B;
}

@@ -55,3 +55,14 @@ function test(
public T3 $z
= 'x',
) {}
-----
<?php
new class {};
new readonly class {};
-----
$stmts[0]->expr->class->flags = Modifiers::READONLY;
$stmts[1]->expr->class->flags = 0;
-----
<?php
readonly class {};
class {};

@@ -35,6 +35,11 @@ Bar
y
;
}
const
int
X
= 1;
}
$foo [ $bar ];
@@ -97,6 +102,7 @@ $stmts[2]->extends = null;
$stmts[2]->stmts[0]->returnType = null;
$stmts[2]->stmts[1]->props[0]->default = null;
$stmts[2]->stmts[2]->adaptations[0]->newName = null;
$stmts[2]->stmts[3]->type = null;
$stmts[3]->expr->dim = null;
$stmts[4]->expr->expr = null;
$stmts[5]->expr->if = null;
@@ -141,6 +147,10 @@ Foo
public
;
}
const
X
= 1;
}
$foo [];

@@ -33,4 +33,4 @@ array(
0: // empty
)
)
)
)

@@ -105,4 +105,4 @@ array(
0: // comment
)
)
)
)

@@ -7,9 +7,7 @@ array(
0: Stmt_Expression(
expr: Expr_ConstFetch(
name: Name(
parts: array(
0: foo
)
name: foo
)
)
)
@@ -22,9 +20,7 @@ array(
0: Stmt_Expression(
expr: Expr_ConstFetch(
name: Name(
parts: array(
0: foo
)
name: foo
)
)
)
@@ -33,4 +29,4 @@ array(
0: /* bar */
)
)
)
)

@@ -13,9 +13,7 @@ array(
0: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: foo
)
name: foo
)
args: array(
)
@@ -24,9 +22,7 @@ array(
1: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: bar
)
name: bar
)
args: array(
)
@@ -35,9 +31,7 @@ array(
2: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: baz
)
name: baz
)
args: array(
)
@@ -56,9 +50,7 @@ array(
0: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: foo
)
name: foo
)
args: array(
)
@@ -67,9 +59,7 @@ array(
1: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: bar
)
name: bar
)
args: array(
)
@@ -78,9 +68,7 @@ array(
2: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: baz
)
name: baz
)
args: array(
)
@@ -99,9 +87,7 @@ array(
0: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: foo
)
name: foo
)
args: array(
)
@@ -110,9 +96,7 @@ array(
1: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: bar
)
name: bar
)
args: array(
)
@@ -121,9 +105,7 @@ array(
2: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: baz
)
name: baz
)
args: array(
)
@@ -140,9 +122,7 @@ array(
0: Stmt_Expression(
expr: Expr_ConstFetch(
name: Name(
parts: array(
0: abc
)
name: abc
)
)
)
@@ -339,9 +319,7 @@ array(
0: Stmt_Expression(
expr: Expr_New(
class: Name(
parts: array(
0: T
)
name: T
)
args: array(
)
@@ -402,9 +380,7 @@ array(
0: Stmt_Expression(
expr: Expr_StaticPropertyFetch(
class: Name(
parts: array(
0: Foo
)
name: Foo
)
name: Expr_Error(
)
@@ -420,9 +396,7 @@ array(
0: Stmt_Expression(
expr: Expr_ClassConstFetch(
class: Name(
parts: array(
0: Foo
)
name: Foo
)
name: Expr_Error(
)
@@ -455,9 +429,7 @@ Syntax error, unexpected T_THROW, expecting ';' from 15:1 to 15:5
array(
0: Stmt_Namespace(
name: Name(
parts: array(
0: Foo
)
name: Foo
)
stmts: array(
0: Stmt_Use(
@@ -466,9 +438,7 @@ array(
0: UseItem(
type: TYPE_UNKNOWN (0)
name: Name(
parts: array(
0: A
)
name: A
)
alias: null
)
@@ -480,9 +450,7 @@ array(
0: UseItem(
type: TYPE_UNKNOWN (0)
name: Name(
parts: array(
0: a
)
name: a
)
alias: null
)
@@ -491,17 +459,13 @@ array(
2: Stmt_GroupUse(
type: TYPE_UNKNOWN (0)
prefix: Name(
parts: array(
0: A
)
name: A
)
uses: array(
0: UseItem(
type: TYPE_NORMAL (1)
name: Name(
parts: array(
0: B
)
name: B
)
alias: null
)
@@ -619,17 +583,13 @@ array(
0: Stmt_GroupUse(
type: TYPE_UNKNOWN (0)
prefix: Name(
parts: array(
0: A
)
name: A
)
uses: array(
0: UseItem(
type: TYPE_NORMAL (1)
name: Name(
parts: array(
0: B
)
name: B
)
alias: null
)
@@ -638,17 +598,13 @@ array(
1: Stmt_GroupUse(
type: TYPE_FUNCTION (2)
prefix: Name(
parts: array(
0: A
)
name: A
)
uses: array(
0: UseItem(
type: TYPE_UNKNOWN (0)
name: Name(
parts: array(
0: b
)
name: b
)
alias: null
)
@@ -660,9 +616,7 @@ array(
0: UseItem(
type: TYPE_UNKNOWN (0)
name: Name(
parts: array(
0: A
)
name: A
)
alias: null
)
@@ -690,18 +644,14 @@ array(
extends: null
implements: array(
0: Name(
parts: array(
0: Y
)
name: Y
)
)
stmts: array(
0: Stmt_TraitUse(
traits: array(
0: Name(
parts: array(
0: A
)
name: A
)
)
adaptations: array(
@@ -710,26 +660,20 @@ array(
1: Stmt_TraitUse(
traits: array(
0: Name(
parts: array(
0: A
)
name: A
)
)
adaptations: array(
0: Stmt_TraitUseAdaptation_Precedence(
trait: Name(
parts: array(
0: A
)
name: A
)
method: Identifier(
name: b
)
insteadof: array(
0: Name(
parts: array(
0: C
)
name: C
)
)
)
@@ -739,6 +683,7 @@ array(
attrGroups: array(
)
flags: 0
type: null
consts: array(
0: Const(
name: Identifier(
@@ -774,9 +719,7 @@ array(
)
extends: array(
0: Name(
parts: array(
0: J
)
name: J
)
)
stmts: array(
@@ -866,18 +809,14 @@ array(
0: Stmt_Expression[3:1 - 3:11](
expr: Expr_FuncCall[3:1 - 3:10](
name: Name[3:1 - 3:3](
parts: array(
0: foo
)
name: foo
)
args: array(
0: Arg[3:5 - 3:9](
name: null
value: Expr_ClassConstFetch[3:5 - 3:9](
class: Name[3:5 - 3:7](
parts: array(
0: Bar
)
name: Bar
)
name: Expr_Error[3:10 - 3:9](
)
@@ -1017,9 +956,7 @@ array(
)
flags: 0
type: Name(
parts: array(
0: Type
)
name: Type
)
byRef: false
variadic: false
@@ -1050,9 +987,7 @@ array(
)
flags: 0
type: Name(
parts: array(
0: Type1
)
name: Type1
)
byRef: false
variadic: false
@@ -1066,9 +1001,7 @@ array(
)
flags: 0
type: Name(
parts: array(
0: Type2
)
name: Type2
)
byRef: false
variadic: false
@@ -1157,9 +1090,7 @@ array(
)
flags: 0
type: Name(
parts: array(
0: Bar
)
name: Bar
)
byRef: false
variadic: false
@@ -1197,9 +1128,7 @@ array(
)
flags: 0
type: Name(
parts: array(
0: Baz
)
name: Baz
)
byRef: false
variadic: false
@@ -1226,9 +1155,7 @@ array(
)
flags: 0
type: Name(
parts: array(
0: Foo
)
name: Foo
)
byRef: false
variadic: false
@@ -1495,6 +1422,7 @@ array(
attrGroups: array(
)
flags: 0
type: null
consts: array(
0: Const(
name: Identifier(

@@ -170,4 +170,4 @@ array(
)
)
)
)
)

@@ -169,4 +169,4 @@ array(
)
)
)
)
)

@@ -67,4 +67,4 @@ array(
)
)
)
)
)

@@ -220,9 +220,7 @@ array(
key: null
value: Expr_FuncCall(
name: Name(
parts: array(
0: getArr
)
name: getArr
)
args: array(
)
@@ -240,9 +238,7 @@ array(
key: null
value: Expr_FuncCall(
name: Name(
parts: array(
0: arrGen
)
name: arrGen
)
args: array(
)
@@ -260,9 +256,7 @@ array(
key: null
value: Expr_New(
class: Name(
parts: array(
0: ArrayIterator
)
name: ArrayIterator
)
args: array(
0: Arg(
@@ -329,9 +323,7 @@ array(
key: null
value: Expr_FuncCall(
name: Name(
parts: array(
0: getArr
)
name: getArr
)
args: array(
)
@@ -383,9 +375,7 @@ array(
key: null
value: Expr_FuncCall(
name: Name(
parts: array(
0: arrGen
)
name: arrGen
)
args: array(
)
@@ -434,4 +424,4 @@ array(
)
)
)
)
)

@@ -260,4 +260,4 @@ array(
)
)
)
)
)

@@ -378,4 +378,4 @@ array(
)
)
)
)
)

@@ -12,9 +12,7 @@ array(
)
expr: Expr_New(
class: Name(
parts: array(
0: B
)
name: B
)
args: array(
)
@@ -36,13 +34,11 @@ array(
)
expr: Expr_New(
class: Name(
parts: array(
0: B
)
name: B
)
args: array(
)
)
)
)
)
)

@@ -91,4 +91,4 @@ array(
)
)
)
)
)

@@ -12,4 +12,4 @@ array(
)
)
)
)
)

@@ -190,13 +190,10 @@ array(
)
)
returnType: Name_FullyQualified(
parts: array(
0: Foo
1: Bar
)
name: Foo\Bar
)
stmts: array(
)
)
)
)
)

@@ -25,4 +25,4 @@ array(
)
)
)
)
)

@@ -110,9 +110,7 @@ array(
name: a
)
class: Name(
parts: array(
0: B
)
name: B
)
)
)
@@ -126,4 +124,4 @@ array(
)
)
)
)
)

@@ -94,4 +94,4 @@ array(
)
)
)
)
)

@@ -682,13 +682,11 @@ array(
value: Expr_BooleanNot(
expr: Expr_ConstFetch(
name: Name(
parts: array(
0: false
)
name: false
)
)
)
)
)
)
)
)

@@ -8,15 +8,11 @@ array(
0: Stmt_Expression(
expr: Expr_ClassConstFetch(
class: Name(
parts: array(
0: Foo
)
name: Foo
)
name: Expr_FuncCall(
name: Name(
parts: array(
0: bar
)
name: bar
)
args: array(
)
@@ -30,9 +26,7 @@ array(
)
name: Expr_FuncCall(
name: Name(
parts: array(
0: bar
)
name: bar
)
args: array(
)

@@ -11,4 +11,4 @@ array(
)
)
)
)
)

@@ -43,4 +43,4 @@ array(
)
)
)
)
)

@@ -45,4 +45,4 @@ array(
0: // This is illegal, but not a syntax error.
)
)
)
)

@@ -77,4 +77,4 @@ array(
0: // This is illegal, but not a syntax error.
)
)
)
)

@@ -12,9 +12,7 @@ array(
0: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: f
)
name: f
)
args: array(
)
@@ -23,9 +21,7 @@ array(
1: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: f
)
name: f
)
args: array(
0: Arg(
@@ -42,9 +38,7 @@ array(
2: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: f
)
name: f
)
args: array(
0: Arg(
@@ -69,9 +63,7 @@ array(
3: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: f
)
name: f
)
args: array(
0: Arg(
@@ -88,9 +80,7 @@ array(
4: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: f
)
name: f
)
args: array(
0: Arg(
@@ -112,4 +102,4 @@ array(
)
)
)
)
)

@@ -12,18 +12,14 @@ array(
0: Stmt_Expression(
expr: Expr_ConstFetch(
name: Name(
parts: array(
0: A
)
name: A
)
)
)
1: Stmt_Expression(
expr: Expr_ClassConstFetch(
class: Name(
parts: array(
0: A
)
name: A
)
name: Identifier(
name: B
@@ -33,9 +29,7 @@ array(
2: Stmt_Expression(
expr: Expr_ClassConstFetch(
class: Name(
parts: array(
0: A
)
name: A
)
name: Identifier(
name: class
@@ -62,4 +56,4 @@ array(
)
)
)
)
)

@@ -210,9 +210,7 @@ array(
expr: Expr_ArrayDimFetch(
var: Expr_ConstFetch(
name: Name(
parts: array(
0: FOO
)
name: FOO
)
)
dim: Scalar_Int(
@@ -224,9 +222,7 @@ array(
expr: Expr_ArrayDimFetch(
var: Expr_ClassConstFetch(
class: Name(
parts: array(
0: Foo
)
name: Foo
)
name: Identifier(
name: BAR
@@ -262,4 +258,4 @@ array(
)
)
)
)
)

@@ -19,9 +19,7 @@ array(
0: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: a
)
name: a
comments: array(
0: // function name variations
)
@@ -131,9 +129,7 @@ array(
expr: Expr_ArrayDimFetch(
var: Expr_FuncCall(
name: Name(
parts: array(
0: a
)
name: a
comments: array(
0: // array dereferencing
)
@@ -155,4 +151,4 @@ array(
0: // array dereferencing
)
)
)
)

@@ -8,9 +8,7 @@ array(
0: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: foo
)
name: foo
)
args: array(
0: Arg(
@@ -39,9 +37,7 @@ array(
1: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: bar
)
name: bar
)
args: array(
0: Arg(
@@ -57,4 +53,4 @@ array(
)
)
)
)
)

@@ -12,9 +12,7 @@ array(
expr: Expr_PropertyFetch(
var: Expr_New(
class: Name(
parts: array(
0: A
)
name: A
)
args: array(
)
@@ -28,9 +26,7 @@ array(
expr: Expr_MethodCall(
var: Expr_New(
class: Name(
parts: array(
0: A
)
name: A
)
args: array(
)
@@ -46,9 +42,7 @@ array(
expr: Expr_ArrayDimFetch(
var: Expr_New(
class: Name(
parts: array(
0: A
)
name: A
)
args: array(
)
@@ -63,9 +57,7 @@ array(
var: Expr_ArrayDimFetch(
var: Expr_New(
class: Name(
parts: array(
0: A
)
name: A
)
args: array(
)
@@ -79,4 +71,4 @@ array(
)
)
)
)
)

@@ -177,4 +177,4 @@ array(
)
)
)
)
)

@@ -69,4 +69,4 @@ array(
)
)
)
)
)

@@ -22,9 +22,7 @@ array(
0: Stmt_Expression(
expr: Expr_StaticCall(
class: Name(
parts: array(
0: A
)
name: A
comments: array(
0: // method name variations
)
@@ -45,9 +43,7 @@ array(
1: Stmt_Expression(
expr: Expr_StaticCall(
class: Name(
parts: array(
0: A
)
name: A
)
name: Scalar_String(
value: b
@@ -59,9 +55,7 @@ array(
2: Stmt_Expression(
expr: Expr_StaticCall(
class: Name(
parts: array(
0: A
)
name: A
)
name: Expr_Variable(
name: b
@@ -75,9 +69,7 @@ array(
name: Expr_ArrayDimFetch(
var: Expr_StaticPropertyFetch(
class: Name(
parts: array(
0: A
)
name: A
)
name: VarLikeIdentifier(
name: b
@@ -97,9 +89,7 @@ array(
var: Expr_ArrayDimFetch(
var: Expr_StaticPropertyFetch(
class: Name(
parts: array(
0: A
)
name: A
)
name: VarLikeIdentifier(
name: b
@@ -121,9 +111,7 @@ array(
expr: Expr_ArrayDimFetch(
var: Expr_StaticCall(
class: Name(
parts: array(
0: A
)
name: A
comments: array(
0: // array dereferencing
)
@@ -151,9 +139,7 @@ array(
6: Stmt_Expression(
expr: Expr_StaticCall(
class: Name(
parts: array(
0: static
)
name: static
comments: array(
0: // class name variations
)
@@ -214,4 +200,4 @@ array(
)
)
)
)
)

@@ -17,9 +17,7 @@ array(
0: Stmt_Expression(
expr: Expr_StaticPropertyFetch(
class: Name(
parts: array(
0: A
)
name: A
comments: array(
0: // property name variations
)
@@ -38,9 +36,7 @@ array(
1: Stmt_Expression(
expr: Expr_StaticPropertyFetch(
class: Name(
parts: array(
0: A
)
name: A
)
name: Expr_Variable(
name: b
@@ -50,9 +46,7 @@ array(
2: Stmt_Expression(
expr: Expr_StaticPropertyFetch(
class: Name(
parts: array(
0: A
)
name: A
)
name: Scalar_String(
value: b
@@ -63,9 +57,7 @@ array(
expr: Expr_ArrayDimFetch(
var: Expr_StaticPropertyFetch(
class: Name(
parts: array(
0: A
)
name: A
comments: array(
0: // array access
)
@@ -92,9 +84,7 @@ array(
expr: Expr_ArrayDimFetch(
var: Expr_StaticPropertyFetch(
class: Name(
parts: array(
0: A
)
name: A
)
name: VarLikeIdentifier(
name: b
@@ -110,4 +100,4 @@ array(
0: // class name variations can be found in staticCall.test
)
)
)
)

@@ -16,9 +16,7 @@ array(
0: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: foo
)
name: foo
)
args: array(
0: VariadicPlaceholder(
@@ -43,9 +41,7 @@ array(
2: Stmt_Expression(
expr: Expr_StaticCall(
class: Name(
parts: array(
0: A
)
name: A
)
name: Identifier(
name: foo
@@ -59,9 +55,7 @@ array(
3: Stmt_Expression(
expr: Expr_New(
class: Name(
parts: array(
0: Foo
)
name: Foo
)
args: array(
0: VariadicPlaceholder(
@@ -95,9 +89,7 @@ array(
attrs: array(
0: Attribute(
name: Name(
parts: array(
0: Foo
)
name: Foo
)
args: array(
0: VariadicPlaceholder(
@@ -117,4 +109,4 @@ array(
stmts: array(
)
)
)
)

@@ -47,4 +47,4 @@ array(
)
)
)
)
)

@@ -44,9 +44,7 @@ array(
expr: Expr_Empty(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: foo
)
name: foo
)
args: array(
)
@@ -85,4 +83,4 @@ array(
)
)
)
)
)

@@ -14,55 +14,41 @@ private\protected\public\static\abstract\final();
array(
0: Stmt_Namespace(
name: Name(
parts: array(
0: fn
)
name: fn
)
stmts: array(
)
)
1: Stmt_Namespace(
name: Name(
parts: array(
0: fn
1: use
)
name: fn\use
)
stmts: array(
)
)
2: Stmt_Namespace(
name: Name(
parts: array(
0: self
)
name: self
)
stmts: array(
)
)
3: Stmt_Namespace(
name: Name(
parts: array(
0: parent
)
name: parent
)
stmts: array(
)
)
4: Stmt_Namespace(
name: Name(
parts: array(
0: static
)
name: static
)
stmts: array(
0: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: fn
1: use
)
name: fn\use
)
args: array(
)
@@ -71,10 +57,7 @@ array(
1: Stmt_Expression(
expr: Expr_FuncCall(
name: Name_FullyQualified(
parts: array(
0: fn
1: use
)
name: fn\use
)
args: array(
)
@@ -83,10 +66,7 @@ array(
2: Stmt_Expression(
expr: Expr_FuncCall(
name: Name_Relative(
parts: array(
0: fn
1: use
)
name: fn\use
)
args: array(
)
@@ -95,14 +75,7 @@ array(
3: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
parts: array(
0: private
1: protected
2: public
3: static
4: abstract
5: final
)
name: private\protected\public\static\abstract\final
)
args: array(
)
@@ -110,4 +83,4 @@ array(
)
)
)
)
)

@@ -88,4 +88,4 @@ array(
)
)
)
)
)

@@ -80,4 +80,4 @@ array(
)
)
)
)
)

@@ -187,4 +187,4 @@ array(
)
)
)
)
)

@@ -105,9 +105,7 @@ array(
conds: array(
0: Expr_ClassConstFetch(
class: Name(
parts: array(
0: BinaryOperator
)
name: BinaryOperator
)
name: Identifier(
name: ADD
@@ -210,4 +208,4 @@ array(
)
)
)
)
)

@@ -310,4 +310,4 @@ array(
)
)
)
)
)

@@ -22,9 +22,7 @@ array(
0: Stmt_Expression(
expr: Expr_New(
class: Name(
parts: array(
0: A
)
name: A
)
args: array(
)
@@ -33,9 +31,7 @@ array(
1: Stmt_Expression(
expr: Expr_New(
class: Name(
parts: array(
0: A
)
name: A
)
args: array(
0: Arg(
@@ -82,9 +78,7 @@ array(
expr: Expr_New(
class: Expr_StaticPropertyFetch(
class: Name(
parts: array(
0: A
)
name: A
)
name: VarLikeIdentifier(
name: b
@@ -174,9 +168,7 @@ array(
9: Stmt_Expression(
expr: Expr_New(
class: Name(
parts: array(
0: A
)
name: A
)
args: array(
)
@@ -185,4 +177,4 @@ array(
0: // test regression introduces by new dereferencing syntax
)
)
)
)

@@ -13,4 +13,4 @@ array(
)
)
)
)
)

@@ -11,4 +11,4 @@ array(
)
)
)
)
)

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