Compare commits

..

30 Commits

Author SHA1 Message Date
dc47765670 Support every argument syntax for clone() (#1092)
See php/php-src#18938
2025-07-02 22:09:49 +02:00
8ab65b4adc Support declaring functions with name clone (#1090)
For use in stubs.

see 9c7a3f8d8f
see https://wiki.php.net/rfc/clone_with_v2
see php/php-src#18919
2025-06-23 14:35:24 +02:00
7fc3bcf970 Minor spelling now longer -> no longer (#1088) 2025-06-01 10:25:00 +02:00
acf8f5ef01 Minor spelling creatForHostVersion() -> createForHostVersion() (#1087) 2025-06-01 10:24:40 +02:00
ae59794362 Release PHP-Parser 5.5.0 2025-05-31 10:24:38 +02:00
4964f8d5dd Add support for attributes on constants
Just merged in php/php-src@3f03f7ed3d, updating
this parser is needed to be able to use attributes on constants in the stub
files.
2025-04-29 23:19:04 +02:00
20b0d55f66 Use multi-line attributes for params when targeting PHP <8.0
When the pretty printer targets a version older than PHP 8,
print the attributes for parameters on a separate line, so that
they are interpreted as comments.

Fixes https://github.com/nikic/PHP-Parser/issues/1081.
2025-04-24 22:08:26 +02:00
c9d0b6c9f2 Support WeakReferences in NodeConnectingVisitor (#1057)
Add a constructor argument which enables the use of WeakReferences.
The attributes are called weak_parent, weak_next and weak_previous
in that case.
2025-02-26 20:51:23 +01:00
14f9c9df7f Use more precise list-types for getter methods 2025-02-18 22:17:03 +01:00
fa02db3f30 Remove useless foreach key and cast string, and fix invalid @var definition in test (#1061) 2025-01-17 22:41:39 +01:00
d4fce83c2c Remove useless cast (string) and @var doc 2025-01-16 21:27:18 +01:00
7d3039c378 Update to phpstan 2.0 2024-12-30 12:14:29 +01:00
447a020a1f Release PHP-Parser 5.4.0 2024-12-30 12:07:19 +01:00
45f70ed80a Simplify NodeTraverser->traverseNode() 2024-12-30 11:10:07 +01:00
74a361814d Simplify NodeTraverser->traverseArray() 2024-12-30 11:10:07 +01:00
6478c5ac53 Fix PropertyHook::getStmts() for set hook
Produce the correct desugaring if the propertyName attribute is
set, and set it in the parser. Otherwise throw an exception.

Fixes #1053.
2024-12-27 16:56:00 +01:00
8bb415902e Fix cs 2024-12-27 16:33:38 +01:00
48fd76e542 Make PropertyHook::getStmts() less incorrect
Still missing the assignment to the correct property, but at
least we're not returning from a non-void function now...
2024-12-27 16:30:03 +01:00
d20a197ca7 Emit error - Multiple properties cannot share the same hooks
Closes GH-1052.
2024-12-27 16:19:46 +01:00
62dee28027 Rename ParserAbstract method checkPropertyHookList to checkEmptyPropertyHookList 2024-12-27 16:13:47 +01:00
b396e9e0d6 Add missing newline after usage message 2024-12-27 16:13:29 +01:00
f212bb7afb Add PropertyHook::isFinal() helper method with tests 2024-12-13 23:15:12 +01:00
f43324a074 Missing flags subNode description in PropertyHook constructor PHPDoc 2024-12-13 23:15:12 +01:00
73b160f8c4 Add flags helper methods Property::isAbstract() and Property::isFinal() 2024-12-13 23:15:12 +01:00
4f9dc8b0f5 Promoted properties with hooks do not need visibility modifier 2024-12-11 10:17:55 +01:00
469377f4a8 Fix classmap authoritative autoloading (#1046)
This patch resolves #1045 by adding dummy class definitions
behind if (false) checks that are picked up the authoritative
autoloader.
2024-12-06 22:00:17 +01:00
74d3f7fc24 Removed reudndant token generation 2024-11-24 09:54:02 +01:00
25828ea952 Updated target.php to new PHP-Parser version 2024-11-24 09:54:02 +01:00
05ed79595b Removed unrecheable statement 2024-11-19 21:17:27 +01:00
954f7a411f More precise toLowerString() return type 2024-11-05 21:19:09 +01:00
58 changed files with 4682 additions and 3006 deletions

View File

@ -1,3 +1,40 @@
Version 5.5.0 (2025-05-31)
--------------------------
### Added
* [8.5] Added support for attributes on constants. `Stmt\Const_` now has an `attrGroups` subnode.
* Added `weakReferences` option to `NodeConnectingVisitor` and `ParentConnectingVisitor`. This
will create the parent/next/prev references as WeakReferences, to avoid making the AST cyclic
and thus increasing GC pressure.
### Changed
* Attributes on parameters are now printed on separate lines if the pretty printer target version
is PHP 7.4 or older (which is the default). This allows them to be interpreted as comments,
instead of causing a parse error. Specify a target version of PHP 8.0 or newer to restore the
previous behavior.
Version 5.4.0 (2024-12-30)
--------------------------
### Added
* Added `Property::isAbstract()` and `Property::isFinal()` methods.
* Added `PropertyHook::isFinal()` method.
* Emit an error if property hook is used on declaration with multiple properties.
### Fixed
* Make legacy class aliases compatible with classmap-authoritative autoloader.
* `Param::isPromoted()` and `Param::isPublic()` now returns true for parameters that have property
hooks but no explicit visibility modifier.
* `PropertyHook::getStmts()` now correctly desugars short `set` hooks. `set => $value` will be
expanded to `set { $this->propertyName = $value; }`. This requires the `propertyName` attribute
on the hook to be set, which is now also set by the parser. If the attribute is not set,
`getStmts()` will throw an error for short set hooks, as it is not possible to produce a correct
desugaring.
Version 5.3.1 (2024-10-08) Version 5.3.1 (2024-10-08)
-------------------------- --------------------------

View File

@ -8,3 +8,6 @@ phpstan: tools/vendor
php-cs-fixer: tools/vendor php-cs-fixer: tools/vendor
php tools/vendor/bin/php-cs-fixer fix php tools/vendor/bin/php-cs-fixer fix
tests:
php vendor/bin/phpunit

View File

@ -37,7 +37,7 @@ The `ParserFactory::create()` method has been removed in favor of three new meth
* `createForHostVersion()`: Use this if you're parsing code for the PHP version you're running on. * `createForHostVersion()`: Use this if you're parsing code for the PHP version you're running on.
* `createForVersion()`: Use this if you know the PHP version of the code you want to parse. * `createForVersion()`: Use this if you know the PHP version of the code you want to parse.
The `createForNewestSupportedVersion()` and `creatForHostVersion()` are available since PHP-Parser 4.18.0, to allow libraries to support PHP-Parser 4 and 5 at the same time more easily. The `createForNewestSupportedVersion()` and `createForHostVersion()` are available since PHP-Parser 4.18.0, to allow libraries to support PHP-Parser 4 and 5 at the same time more easily.
In all cases, the PHP version is a fairly weak hint that is only used on a best-effort basis. The parser will usually accept code for newer versions if it does not have any backwards-compatibility implications. In all cases, the PHP version is a fairly weak hint that is only used on a best-effort basis. The parser will usually accept code for newer versions if it does not have any backwards-compatibility implications.
@ -308,7 +308,7 @@ PhpParser\Node\Stmt\Class_::VISIBILITY_MODIFIER_MASK -> PhpParser\Modifiers::VIS
### Changes to node constructors ### 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: Node constructor arguments accepting types no 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\ArrowFunction`.
* The `'returnType'` key of `$subNodes` argument of `Node\Expr\Closure`. * The `'returnType'` key of `$subNodes` argument of `Node\Expr\Closure`.

View File

@ -51,3 +51,17 @@ obtained through `$node->getAttribute('next')`.
`ParentConnectingVisitor` and `NodeConnectingVisitor` should not be used at the same time. The latter `ParentConnectingVisitor` and `NodeConnectingVisitor` should not be used at the same time. The latter
includes the functionality of the former. includes the functionality of the former.
How can I limit the impact of cyclic references in the AST?
-----
NodeConnectingVisitor adds a parent reference, which introduces a cycle. This means that the AST can now only be collected by the cycle garbage collector.
This in turn can lead to performance and/or memory issues.
To break the cyclic references between AST nodes `NodeConnectingVisitor` supports a boolean `$weakReferences` constructor parameter.
When set to `true`, all attributes added by `NodeConnectingVisitor` will be wrapped into a `WeakReference` object.
After enabling this parameter, the parent node can be obtained through `$node->getAttribute('weak_parent')`,
the previous node can be obtained through `$node->getAttribute('weak_previous')`, and the next node can be
obtained through `$node->getAttribute('weak_next')`.

View File

@ -257,7 +257,10 @@ top_statement:
| T_USE use_declarations semi { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; } | T_USE use_declarations semi { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; }
| T_USE use_type use_declarations semi { $$ = Stmt\Use_[$3, $2]; } | T_USE use_type use_declarations semi { $$ = Stmt\Use_[$3, $2]; }
| group_use_declaration | group_use_declaration
| T_CONST constant_declaration_list semi { $$ = Stmt\Const_[$2]; } | T_CONST constant_declaration_list semi { $$ = new Stmt\Const_($2, attributes(), []); }
| attributes T_CONST constant_declaration_list semi
{ $$ = new Stmt\Const_($3, attributes(), $1);
$this->checkConstantAttributes($$); }
; ;
use_type: use_type:
@ -468,6 +471,7 @@ fn_identifier:
identifier_not_reserved identifier_not_reserved
| T_READONLY { $$ = Node\Identifier[$1]; } | T_READONLY { $$ = Node\Identifier[$1]; }
| T_EXIT { $$ = Node\Identifier[$1]; } | T_EXIT { $$ = Node\Identifier[$1]; }
| T_CLONE { $$ = Node\Identifier[$1]; }
; ;
function_declaration_statement: function_declaration_statement:
@ -686,11 +690,13 @@ parameter:
optional_attributes optional_property_modifiers optional_type_without_static optional_attributes optional_property_modifiers optional_type_without_static
optional_arg_ref optional_ellipsis plain_variable optional_property_hook_list optional_arg_ref optional_ellipsis plain_variable optional_property_hook_list
{ $$ = new Node\Param($6, null, $3, $4, $5, attributes(), $2, $1, $7); { $$ = new Node\Param($6, null, $3, $4, $5, attributes(), $2, $1, $7);
$this->checkParam($$); } $this->checkParam($$);
$this->addPropertyNameToHooks($$); }
| optional_attributes optional_property_modifiers optional_type_without_static | optional_attributes optional_property_modifiers optional_type_without_static
optional_arg_ref optional_ellipsis plain_variable '=' expr optional_property_hook_list optional_arg_ref optional_ellipsis plain_variable '=' expr optional_property_hook_list
{ $$ = new Node\Param($6, $8, $3, $4, $5, attributes(), $2, $1, $9); { $$ = new Node\Param($6, $8, $3, $4, $5, attributes(), $2, $1, $9);
$this->checkParam($$); } $this->checkParam($$);
$this->addPropertyNameToHooks($$); }
| optional_attributes optional_property_modifiers optional_type_without_static | optional_attributes optional_property_modifiers optional_type_without_static
optional_arg_ref optional_ellipsis error optional_arg_ref optional_ellipsis error
{ $$ = new Node\Param(Expr\Error[], null, $3, $4, $5, attributes(), $2, $1); } { $$ = new Node\Param(Expr\Error[], null, $3, $4, $5, attributes(), $2, $1); }
@ -779,6 +785,22 @@ argument_list:
| '(' variadic_placeholder ')' { init($2); } | '(' variadic_placeholder ')' { init($2); }
; ;
clone_argument_list:
'(' ')' { $$ = array(); }
| '(' non_empty_clone_argument_list optional_comma ')' { $$ = $2; }
| '(' expr ',' ')' { init(Node\Arg[$2, false, false]); }
| '(' variadic_placeholder ')' { init($2); }
;
non_empty_clone_argument_list:
expr ',' argument
{ init(new Node\Arg($1, false, false, stackAttributes(#1)), $3); }
| argument_no_expr
{ init($1); }
| non_empty_clone_argument_list ',' argument
{ push($1, $3); }
;
variadic_placeholder: variadic_placeholder:
T_ELLIPSIS { $$ = Node\VariadicPlaceholder[]; } T_ELLIPSIS { $$ = Node\VariadicPlaceholder[]; }
; ;
@ -788,14 +810,18 @@ non_empty_argument_list:
| non_empty_argument_list ',' argument { push($1, $3); } | non_empty_argument_list ',' argument { push($1, $3); }
; ;
argument: argument_no_expr:
expr { $$ = Node\Arg[$1, false, false]; } ampersand variable { $$ = Node\Arg[$2, true, false]; }
| ampersand variable { $$ = Node\Arg[$2, true, false]; }
| T_ELLIPSIS expr { $$ = Node\Arg[$2, false, true]; } | T_ELLIPSIS expr { $$ = Node\Arg[$2, false, true]; }
| identifier_maybe_reserved ':' expr | identifier_maybe_reserved ':' expr
{ $$ = new Node\Arg($3, false, false, attributes(), $1); } { $$ = new Node\Arg($3, false, false, attributes(), $1); }
; ;
argument:
expr { $$ = Node\Arg[$1, false, false]; }
| argument_no_expr { $$ = $1; }
;
global_var_list: global_var_list:
non_empty_global_var_list no_comma non_empty_global_var_list no_comma
; ;
@ -840,7 +866,9 @@ class_statement:
#if PHP8 #if PHP8
| optional_attributes variable_modifiers optional_type_without_static property_declaration_list '{' property_hook_list '}' | optional_attributes variable_modifiers optional_type_without_static property_declaration_list '{' property_hook_list '}'
{ $$ = new Stmt\Property($2, $4, attributes(), $3, $1, $6); { $$ = new Stmt\Property($2, $4, attributes(), $3, $1, $6);
$this->checkPropertyHookList($6, #5); } $this->checkPropertyHooksForMultiProperty($$, #5);
$this->checkEmptyPropertyHookList($6, #5);
$this->addPropertyNameToHooks($$); }
#endif #endif
| optional_attributes method_modifiers T_CONST class_const_list semi | optional_attributes method_modifiers T_CONST class_const_list semi
{ $$ = new Stmt\ClassConst($4, $2, attributes(), $1); { $$ = new Stmt\ClassConst($4, $2, attributes(), $1);
@ -949,7 +977,7 @@ property_hook_list:
optional_property_hook_list: optional_property_hook_list:
/* empty */ { $$ = []; } /* empty */ { $$ = []; }
#if PHP8 #if PHP8
| '{' property_hook_list '}' { $$ = $2; $this->checkPropertyHookList($2, #1); } | '{' property_hook_list '}' { $$ = $2; $this->checkEmptyPropertyHookList($2, #1); }
#endif #endif
; ;
@ -1007,6 +1035,7 @@ expr:
} }
| new_expr | new_expr
| match | match
| T_CLONE clone_argument_list { $$ = Expr\FuncCall[new Node\Name($1, stackAttributes(#1)), $2]; }
| T_CLONE expr { $$ = Expr\Clone_[$2]; } | T_CLONE expr { $$ = Expr\Clone_[$2]; }
| variable T_PLUS_EQUAL expr { $$ = Expr\AssignOp\Plus [$1, $3]; } | variable T_PLUS_EQUAL expr { $$ = Expr\AssignOp\Plus [$1, $3]; }
| variable T_MINUS_EQUAL expr { $$ = Expr\AssignOp\Minus [$1, $3]; } | variable T_MINUS_EQUAL expr { $$ = Expr\AssignOp\Minus [$1, $3]; }

View File

@ -1,3 +1,11 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
require __DIR__ . '/../ArrayItem.php'; require __DIR__ . '/../ArrayItem.php';
if (false) {
// For classmap-authoritative support.
class ArrayItem extends \PhpParser\Node\ArrayItem {
}
}

View File

@ -1,3 +1,11 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
require __DIR__ . '/../ClosureUse.php'; require __DIR__ . '/../ClosureUse.php';
if (false) {
// For classmap-authoritative support.
class ClosureUse extends \PhpParser\Node\ClosureUse {
}
}

View File

@ -53,7 +53,7 @@ class Identifier extends NodeAbstract {
/** /**
* Get lowercased identifier as string. * Get lowercased identifier as string.
* *
* @psalm-return non-empty-string * @psalm-return non-empty-string&lowercase-string
* @return string Lowercased identifier as string * @return string Lowercased identifier as string
*/ */
public function toLowerString(): string { public function toLowerString(): string {

View File

@ -8,7 +8,6 @@ use PhpParser\NodeAbstract;
class MatchArm extends NodeAbstract { class MatchArm extends NodeAbstract {
/** @var null|list<Node\Expr> */ /** @var null|list<Node\Expr> */
public ?array $conds; public ?array $conds;
/** @var Node\Expr */
public Expr $body; public Expr $body;
/** /**

View File

@ -129,7 +129,7 @@ class Name extends NodeAbstract {
* Returns lowercased string representation of the name, without taking the name type into * Returns lowercased string representation of the name, without taking the name type into
* account (e.g., no leading backslash for fully qualified names). * account (e.g., no leading backslash for fully qualified names).
* *
* @psalm-return non-empty-string * @psalm-return non-empty-string&lowercase-string
* @return string Lowercased string representation * @return string Lowercased string representation
*/ */
public function toLowerString(): string { public function toLowerString(): string {

View File

@ -68,11 +68,20 @@ class Param extends NodeAbstract {
* Whether this parameter uses constructor property promotion. * Whether this parameter uses constructor property promotion.
*/ */
public function isPromoted(): bool { public function isPromoted(): bool {
return $this->flags !== 0; return $this->flags !== 0 || $this->hooks !== [];
} }
public function isPublic(): bool { public function isPublic(): bool {
return (bool) ($this->flags & Modifiers::PUBLIC); $public = (bool) ($this->flags & Modifiers::PUBLIC);
if ($public) {
return true;
}
if ($this->hooks === []) {
return false;
}
return ($this->flags & Modifiers::VISIBILITY_MASK) === 0;
} }
public function isProtected(): bool { public function isProtected(): bool {

View File

@ -2,6 +2,11 @@
namespace PhpParser\Node; namespace PhpParser\Node;
use PhpParser\Modifiers;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Return_; use PhpParser\Node\Stmt\Return_;
use PhpParser\NodeAbstract; use PhpParser\NodeAbstract;
@ -30,6 +35,7 @@ class PropertyHook extends NodeAbstract implements FunctionLike {
* params?: Param[], * params?: Param[],
* attrGroups?: AttributeGroup[], * attrGroups?: AttributeGroup[],
* } $subNodes Array of the following optional subnodes: * } $subNodes Array of the following optional subnodes:
* 'flags => 0 : Flags
* 'byRef' => false : Whether hook returns by reference * 'byRef' => false : Whether hook returns by reference
* 'params' => array(): Parameters * 'params' => array(): Parameters
* 'attrGroups' => array(): PHP attribute groups * 'attrGroups' => array(): PHP attribute groups
@ -57,10 +63,31 @@ class PropertyHook extends NodeAbstract implements FunctionLike {
return null; return null;
} }
/**
* Whether the property hook is final.
*/
public function isFinal(): bool {
return (bool) ($this->flags & Modifiers::FINAL);
}
public function getStmts(): ?array { public function getStmts(): ?array {
if ($this->body instanceof Expr) { if ($this->body instanceof Expr) {
$name = $this->name->toLowerString();
if ($name === 'get') {
return [new Return_($this->body)]; return [new Return_($this->body)];
} }
if ($name === 'set') {
if (!$this->hasAttribute('propertyName')) {
throw new \LogicException(
'Can only use getStmts() on a "set" hook if the "propertyName" attribute is set');
}
$propName = $this->getAttribute('propertyName');
$prop = new PropertyFetch(new Variable('this'), (string) $propName);
return [new Expression(new Assign($prop, $this->body))];
}
throw new \LogicException('Unknown property hook "' . $name . '"');
}
return $this->body; return $this->body;
} }

View File

@ -1,3 +1,11 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
require __DIR__ . '/Float_.php'; require __DIR__ . '/Float_.php';
if (false) {
// For classmap-authoritative support.
class DNumber extends Float_ {
}
}

View File

@ -1,3 +1,11 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
require __DIR__ . '/InterpolatedString.php'; require __DIR__ . '/InterpolatedString.php';
if (false) {
// For classmap-authoritative support.
class Encapsed extends InterpolatedString {
}
}

View File

@ -1,3 +1,13 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
use PhpParser\Node\InterpolatedStringPart;
require __DIR__ . '/../InterpolatedStringPart.php'; require __DIR__ . '/../InterpolatedStringPart.php';
if (false) {
// For classmap-authoritative support.
class EncapsedStringPart extends InterpolatedStringPart {
}
}

View File

@ -1,3 +1,11 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
require __DIR__ . '/Int_.php'; require __DIR__ . '/Int_.php';
if (false) {
// For classmap-authoritative support.
class LNumber extends Int_ {
}
}

View File

@ -17,7 +17,7 @@ abstract class ClassLike extends Node\Stmt {
public ?Node\Name $namespacedName; public ?Node\Name $namespacedName;
/** /**
* @return TraitUse[] * @return list<TraitUse>
*/ */
public function getTraitUses(): array { public function getTraitUses(): array {
$traitUses = []; $traitUses = [];
@ -30,7 +30,7 @@ abstract class ClassLike extends Node\Stmt {
} }
/** /**
* @return ClassConst[] * @return list<ClassConst>
*/ */
public function getConstants(): array { public function getConstants(): array {
$constants = []; $constants = [];
@ -43,7 +43,7 @@ abstract class ClassLike extends Node\Stmt {
} }
/** /**
* @return Property[] * @return list<Property>
*/ */
public function getProperties(): array { public function getProperties(): array {
$properties = []; $properties = [];
@ -78,7 +78,7 @@ abstract class ClassLike extends Node\Stmt {
/** /**
* Gets all methods defined directly in this class/interface/trait * Gets all methods defined directly in this class/interface/trait
* *
* @return ClassMethod[] * @return list<ClassMethod>
*/ */
public function getMethods(): array { public function getMethods(): array {
$methods = []; $methods = [];

View File

@ -7,20 +7,28 @@ use PhpParser\Node;
class Const_ extends Node\Stmt { class Const_ extends Node\Stmt {
/** @var Node\Const_[] Constant declarations */ /** @var Node\Const_[] Constant declarations */
public array $consts; public array $consts;
/** @var Node\AttributeGroup[] PHP attribute groups */
public array $attrGroups;
/** /**
* Constructs a const list node. * Constructs a const list node.
* *
* @param Node\Const_[] $consts Constant declarations * @param Node\Const_[] $consts Constant declarations
* @param array<string, mixed> $attributes Additional attributes * @param array<string, mixed> $attributes Additional attributes
* @param list<Node\AttributeGroup> $attrGroups PHP attribute groups
*/ */
public function __construct(array $consts, array $attributes = []) { public function __construct(
array $consts,
array $attributes = [],
array $attrGroups = []
) {
$this->attributes = $attributes; $this->attributes = $attributes;
$this->attrGroups = $attrGroups;
$this->consts = $consts; $this->consts = $consts;
} }
public function getSubNodeNames(): array { public function getSubNodeNames(): array {
return ['consts']; return ['attrGroups', 'consts'];
} }
public function getType(): string { public function getType(): string {

View File

@ -1,3 +1,13 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node\DeclareItem;
require __DIR__ . '/../DeclareItem.php'; require __DIR__ . '/../DeclareItem.php';
if (false) {
// For classmap-authoritative support.
class DeclareDeclare extends DeclareItem {
}
}

View File

@ -80,6 +80,20 @@ class Property extends Node\Stmt {
return (bool) ($this->flags & Modifiers::READONLY); return (bool) ($this->flags & Modifiers::READONLY);
} }
/**
* Whether the property is abstract.
*/
public function isAbstract(): bool {
return (bool) ($this->flags & Modifiers::ABSTRACT);
}
/**
* Whether the property is final.
*/
public function isFinal(): bool {
return (bool) ($this->flags & Modifiers::FINAL);
}
/** /**
* Whether the property has explicit public(set) visibility. * Whether the property has explicit public(set) visibility.
*/ */

View File

@ -1,3 +1,13 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node\PropertyItem;
require __DIR__ . '/../PropertyItem.php'; require __DIR__ . '/../PropertyItem.php';
if (false) {
// For classmap-authoritative support.
class PropertyProperty extends PropertyItem {
}
}

View File

@ -1,3 +1,11 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
require __DIR__ . '/../StaticVar.php'; require __DIR__ . '/../StaticVar.php';
if (false) {
// For classmap-authoritative support.
class StaticVar extends \PhpParser\Node\StaticVar {
}
}

View File

@ -1,3 +1,13 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node\UseItem;
require __DIR__ . '/../UseItem.php'; require __DIR__ . '/../UseItem.php';
if (false) {
// For classmap-authoritative support.
class UseUse extends UseItem {
}
}

View File

@ -145,7 +145,7 @@ class NodeDumper {
} elseif ($node instanceof Comment) { } elseif ($node instanceof Comment) {
$this->res .= \str_replace("\n", $this->nl, $node->getReformattedText()); $this->res .= \str_replace("\n", $this->nl, $node->getReformattedText());
} elseif (\is_string($node)) { } elseif (\is_string($node)) {
$this->res .= \str_replace("\n", $this->nl, (string)$node); $this->res .= \str_replace("\n", $this->nl, $node);
} elseif (\is_int($node) || \is_float($node)) { } elseif (\is_int($node) || \is_float($node)) {
$this->res .= $node; $this->res .= $node;
} elseif (null === $node) { } elseif (null === $node) {

View File

@ -99,7 +99,14 @@ class NodeTraverser implements NodeTraverserInterface {
if ($this->stopTraversal) { if ($this->stopTraversal) {
break; break;
} }
} elseif ($subNode instanceof Node) {
continue;
}
if (!$subNode instanceof Node) {
continue;
}
$traverseChildren = true; $traverseChildren = true;
$visitorIndex = -1; $visitorIndex = -1;
@ -163,7 +170,6 @@ class NodeTraverser implements NodeTraverserInterface {
} }
} }
} }
}
/** /**
* Recursively traverse array (usually of nodes). * Recursively traverse array (usually of nodes).
@ -176,7 +182,13 @@ class NodeTraverser implements NodeTraverserInterface {
$doNodes = []; $doNodes = [];
foreach ($nodes as $i => $node) { foreach ($nodes as $i => $node) {
if ($node instanceof Node) { if (!$node instanceof Node) {
if (\is_array($node)) {
throw new \LogicException('Invalid node structure: Contains nested arrays');
}
continue;
}
$traverseChildren = true; $traverseChildren = true;
$visitorIndex = -1; $visitorIndex = -1;
@ -245,9 +257,6 @@ class NodeTraverser implements NodeTraverserInterface {
} }
} }
} }
} elseif (\is_array($node)) {
throw new \LogicException('Invalid node structure: Contains nested arrays');
}
} }
if (!empty($doNodes)) { if (!empty($doNodes)) {

View File

@ -12,7 +12,7 @@ use PhpParser\NodeVisitorAbstract;
class FindingVisitor extends NodeVisitorAbstract { class FindingVisitor extends NodeVisitorAbstract {
/** @var callable Filter callback */ /** @var callable Filter callback */
protected $filterCallback; protected $filterCallback;
/** @var Node[] Found nodes */ /** @var list<Node> Found nodes */
protected array $foundNodes; protected array $foundNodes;
public function __construct(callable $filterCallback) { public function __construct(callable $filterCallback) {
@ -24,7 +24,7 @@ class FindingVisitor extends NodeVisitorAbstract {
* *
* Nodes are returned in pre-order. * Nodes are returned in pre-order.
* *
* @return Node[] Found nodes * @return list<Node> Found nodes
*/ */
public function getFoundNodes(): array { public function getFoundNodes(): array {
return $this->foundNodes; return $this->foundNodes;

View File

@ -120,6 +120,7 @@ class NameResolver extends NodeVisitorAbstract {
foreach ($node->consts as $const) { foreach ($node->consts as $const) {
$this->addNamespacedName($const); $this->addNamespacedName($const);
} }
$this->resolveAttrGroups($node);
} elseif ($node instanceof Stmt\ClassConst) { } elseif ($node instanceof Stmt\ClassConst) {
if (null !== $node->type) { if (null !== $node->type) {
$node->type = $this->resolveType($node->type); $node->type = $this->resolveType($node->type);

View File

@ -9,10 +9,12 @@ use PhpParser\NodeVisitorAbstract;
* Visitor that connects a child node to its parent node * Visitor that connects a child node to its parent node
* as well as its sibling nodes. * as well as its sibling nodes.
* *
* On the child node, the parent node can be accessed through * With <code>$weakReferences=false</code> on the child node, the parent node can be accessed through
* <code>$node->getAttribute('parent')</code>, the previous * <code>$node->getAttribute('parent')</code>, the previous
* node can be accessed through <code>$node->getAttribute('previous')</code>, * node can be accessed through <code>$node->getAttribute('previous')</code>,
* and the next node can be accessed through <code>$node->getAttribute('next')</code>. * and the next node can be accessed through <code>$node->getAttribute('next')</code>.
*
* With <code>$weakReferences=true</code> attribute names are prefixed by "weak_", e.g. "weak_parent".
*/ */
final class NodeConnectingVisitor extends NodeVisitorAbstract { final class NodeConnectingVisitor extends NodeVisitorAbstract {
/** /**
@ -25,6 +27,12 @@ final class NodeConnectingVisitor extends NodeVisitorAbstract {
*/ */
private $previous; private $previous;
private bool $weakReferences;
public function __construct(bool $weakReferences = false) {
$this->weakReferences = $weakReferences;
}
public function beforeTraverse(array $nodes) { public function beforeTraverse(array $nodes) {
$this->stack = []; $this->stack = [];
$this->previous = null; $this->previous = null;
@ -32,13 +40,27 @@ final class NodeConnectingVisitor extends NodeVisitorAbstract {
public function enterNode(Node $node) { public function enterNode(Node $node) {
if (!empty($this->stack)) { if (!empty($this->stack)) {
$node->setAttribute('parent', $this->stack[count($this->stack) - 1]); $parent = $this->stack[count($this->stack) - 1];
if ($this->weakReferences) {
$node->setAttribute('weak_parent', \WeakReference::create($parent));
} else {
$node->setAttribute('parent', $parent);
}
} }
if ($this->previous !== null && $this->previous->getAttribute('parent') === $node->getAttribute('parent')) { if ($this->previous !== null) {
if (
$this->weakReferences
) {
if ($this->previous->getAttribute('weak_parent') === $node->getAttribute('weak_parent')) {
$node->setAttribute('weak_previous', \WeakReference::create($this->previous));
$this->previous->setAttribute('weak_next', \WeakReference::create($node));
}
} elseif ($this->previous->getAttribute('parent') === $node->getAttribute('parent')) {
$node->setAttribute('previous', $this->previous); $node->setAttribute('previous', $this->previous);
$this->previous->setAttribute('next', $node); $this->previous->setAttribute('next', $node);
} }
}
$this->stack[] = $node; $this->stack[] = $node;
} }

View File

@ -11,8 +11,10 @@ use function count;
/** /**
* Visitor that connects a child node to its parent node. * Visitor that connects a child node to its parent node.
* *
* On the child node, the parent node can be accessed through * With <code>$weakReferences=false</code> on the child node, the parent node can be accessed through
* <code>$node->getAttribute('parent')</code>. * <code>$node->getAttribute('parent')</code>.
*
* With <code>$weakReferences=true</code> the attribute name is "weak_parent" instead.
*/ */
final class ParentConnectingVisitor extends NodeVisitorAbstract { final class ParentConnectingVisitor extends NodeVisitorAbstract {
/** /**
@ -20,13 +22,24 @@ final class ParentConnectingVisitor extends NodeVisitorAbstract {
*/ */
private array $stack = []; private array $stack = [];
private bool $weakReferences;
public function __construct(bool $weakReferences = false) {
$this->weakReferences = $weakReferences;
}
public function beforeTraverse(array $nodes) { public function beforeTraverse(array $nodes) {
$this->stack = []; $this->stack = [];
} }
public function enterNode(Node $node) { public function enterNode(Node $node) {
if (!empty($this->stack)) { if (!empty($this->stack)) {
$node->setAttribute('parent', $this->stack[count($this->stack) - 1]); $parent = $this->stack[count($this->stack) - 1];
if ($this->weakReferences) {
$node->setAttribute('weak_parent', \WeakReference::create($parent));
} else {
$node->setAttribute('parent', $parent);
}
} }
$this->stack[] = $node; $this->stack[] = $node;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -23,6 +23,7 @@ use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassConst; use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Const_;
use PhpParser\Node\Stmt\Else_; use PhpParser\Node\Stmt\Else_;
use PhpParser\Node\Stmt\ElseIf_; use PhpParser\Node\Stmt\ElseIf_;
use PhpParser\Node\Stmt\Enum_; use PhpParser\Node\Stmt\Enum_;
@ -32,6 +33,7 @@ use PhpParser\Node\Stmt\Nop;
use PhpParser\Node\Stmt\Property; use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\TryCatch; use PhpParser\Node\Stmt\TryCatch;
use PhpParser\Node\UseItem; use PhpParser\Node\UseItem;
use PhpParser\Node\VarLikeIdentifier;
use PhpParser\NodeVisitor\CommentAnnotatingVisitor; use PhpParser\NodeVisitor\CommentAnnotatingVisitor;
abstract class ParserAbstract implements Parser { abstract class ParserAbstract implements Parser {
@ -411,8 +413,6 @@ abstract class ParserAbstract implements Parser {
$rule = $state - $this->numNonLeafStates; $rule = $state - $this->numNonLeafStates;
} }
} }
throw new \RuntimeException('Reached end of parser loop');
} }
protected function emitError(Error $error): void { protected function emitError(Error $error): void {
@ -1160,8 +1160,15 @@ abstract class ParserAbstract implements Parser {
} }
} }
protected function checkPropertyHooksForMultiProperty(Property $property, int $hookPos): void {
if (count($property->props) > 1) {
$this->emitError(new Error(
'Cannot use hooks when declaring multiple properties', $this->getAttributesAt($hookPos)));
}
}
/** @param PropertyHook[] $hooks */ /** @param PropertyHook[] $hooks */
protected function checkPropertyHookList(array $hooks, int $hookPos): void { protected function checkEmptyPropertyHookList(array $hooks, int $hookPos): void {
if (empty($hooks)) { if (empty($hooks)) {
$this->emitError(new Error( $this->emitError(new Error(
'Property hook list cannot be empty', $this->getAttributesAt($hookPos))); 'Property hook list cannot be empty', $this->getAttributesAt($hookPos)));
@ -1196,6 +1203,27 @@ abstract class ParserAbstract implements Parser {
} }
} }
protected function checkConstantAttributes(Const_ $node): void {
if ($node->attrGroups !== [] && count($node->consts) > 1) {
$this->emitError(new Error(
'Cannot use attributes on multiple constants at once', $node->getAttributes()));
}
}
/**
* @param Property|Param $node
*/
protected function addPropertyNameToHooks(Node $node): void {
if ($node instanceof Property) {
$name = $node->props[0]->name->toString();
} else {
$name = $node->var->name;
}
foreach ($node->hooks as $hook) {
$hook->setAttribute('propertyName', $name);
}
}
/** @param array<Node\Arg|Node\VariadicPlaceholder> $args */ /** @param array<Node\Arg|Node\VariadicPlaceholder> $args */
private function isSimpleExit(array $args): bool { private function isSimpleExit(array $args): bool {
if (\count($args) === 0) { if (\count($args) === 0) {

View File

@ -161,4 +161,11 @@ class PhpVersion {
public function supportsUnicodeEscapes(): bool { public function supportsUnicodeEscapes(): bool {
return $this->id >= 70000; return $this->id >= 70000;
} }
/*
* Whether this version supports attributes.
*/
public function supportsAttributes(): bool {
return $this->id >= 80000;
}
} }

View File

@ -17,7 +17,7 @@ class Standard extends PrettyPrinterAbstract {
// Special nodes // Special nodes
protected function pParam(Node\Param $node): string { protected function pParam(Node\Param $node): string {
return $this->pAttrGroups($node->attrGroups, true) return $this->pAttrGroups($node->attrGroups, $this->phpVersion->supportsAttributes())
. $this->pModifiers($node->flags) . $this->pModifiers($node->flags)
. ($node->type ? $this->p($node->type) . ' ' : '') . ($node->type ? $this->p($node->type) . ' ' : '')
. ($node->byRef ? '&' : '') . ($node->byRef ? '&' : '')
@ -656,7 +656,7 @@ class Standard extends PrettyPrinterAbstract {
return $this->pAttrGroups($node->attrGroups, true) return $this->pAttrGroups($node->attrGroups, true)
. $this->pStatic($node->static) . $this->pStatic($node->static)
. 'function ' . ($node->byRef ? '&' : '') . 'function ' . ($node->byRef ? '&' : '')
. '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')' . '(' . $this->pParams($node->params) . ')'
. (!empty($node->uses) ? ' use (' . $this->pCommaSeparated($node->uses) . ')' : '') . (!empty($node->uses) ? ' use (' . $this->pCommaSeparated($node->uses) . ')' : '')
. (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '')
. ' {' . $this->pStmts($node->stmts) . $this->nl . '}'; . ' {' . $this->pStmts($node->stmts) . $this->nl . '}';
@ -688,7 +688,7 @@ class Standard extends PrettyPrinterAbstract {
$this->pAttrGroups($node->attrGroups, true) $this->pAttrGroups($node->attrGroups, true)
. $this->pStatic($node->static) . $this->pStatic($node->static)
. 'fn' . ($node->byRef ? '&' : '') . 'fn' . ($node->byRef ? '&' : '')
. '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')' . '(' . $this->pParams($node->params) . ')'
. (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '')
. ' => ', . ' => ',
$node->expr, $precedence, $lhsPrecedence); $node->expr, $precedence, $lhsPrecedence);
@ -845,7 +845,7 @@ class Standard extends PrettyPrinterAbstract {
return $this->pAttrGroups($node->attrGroups) return $this->pAttrGroups($node->attrGroups)
. $this->pModifiers($node->flags) . $this->pModifiers($node->flags)
. ($node->byRef ? '&' : '') . $node->name . ($node->byRef ? '&' : '') . $node->name
. ($node->params ? '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')' : '') . ($node->params ? '(' . $this->pParams($node->params) . ')' : '')
. (\is_array($node->body) ? ' {' . $this->pStmts($node->body) . $this->nl . '}' . (\is_array($node->body) ? ' {' . $this->pStmts($node->body) . $this->nl . '}'
: ($node->body !== null ? ' => ' . $this->p($node->body) : '') . ';'); : ($node->body !== null ? ' => ' . $this->p($node->body) : '') . ';');
} }
@ -854,7 +854,7 @@ class Standard extends PrettyPrinterAbstract {
return $this->pAttrGroups($node->attrGroups) return $this->pAttrGroups($node->attrGroups)
. $this->pModifiers($node->flags) . $this->pModifiers($node->flags)
. 'function ' . ($node->byRef ? '&' : '') . $node->name . 'function ' . ($node->byRef ? '&' : '') . $node->name
. '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')' . '(' . $this->pParams($node->params) . ')'
. (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '')
. (null !== $node->stmts . (null !== $node->stmts
? $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}' ? $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'
@ -872,13 +872,15 @@ class Standard extends PrettyPrinterAbstract {
protected function pStmt_Function(Stmt\Function_ $node): string { protected function pStmt_Function(Stmt\Function_ $node): string {
return $this->pAttrGroups($node->attrGroups) return $this->pAttrGroups($node->attrGroups)
. 'function ' . ($node->byRef ? '&' : '') . $node->name . 'function ' . ($node->byRef ? '&' : '') . $node->name
. '(' . $this->pMaybeMultiline($node->params, $this->phpVersion->supportsTrailingCommaInParamList()) . ')' . '(' . $this->pParams($node->params) . ')'
. (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '')
. $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}';
} }
protected function pStmt_Const(Stmt\Const_ $node): string { protected function pStmt_Const(Stmt\Const_ $node): string {
return 'const ' . $this->pCommaSeparated($node->consts) . ';'; return $this->pAttrGroups($node->attrGroups)
. 'const '
. $this->pCommaSeparated($node->consts) . ';';
} }
protected function pStmt_Declare(Stmt\Declare_ $node): string { protected function pStmt_Declare(Stmt\Declare_ $node): string {
@ -1179,6 +1181,27 @@ class Standard extends PrettyPrinterAbstract {
} }
} }
/** @param Node\Param[] $params
*/
private function hasParamWithAttributes(array $params): bool {
foreach ($params as $param) {
if ($param->attrGroups) {
return true;
}
}
return false;
}
/** @param Node\Param[] $params */
protected function pParams(array $params): string {
if ($this->hasNodeWithComments($params) ||
($this->hasParamWithAttributes($params) && !$this->phpVersion->supportsAttributes())
) {
return $this->pCommaSeparatedMultiline($params, $this->phpVersion->supportsTrailingCommaInParamList()) . $this->nl;
}
return $this->pCommaSeparated($params);
}
/** @param Node\AttributeGroup[] $nodes */ /** @param Node\AttributeGroup[] $nodes */
protected function pAttrGroups(array $nodes, bool $inline = false): string { protected function pAttrGroups(array $nodes, bool $inline = false): string {
$result = ''; $result = '';

View File

@ -1634,6 +1634,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
Stmt\Trait_::class . '->attrGroups' => [null, '', "\n"], Stmt\Trait_::class . '->attrGroups' => [null, '', "\n"],
Expr\ArrowFunction::class . '->attrGroups' => [null, '', ' '], Expr\ArrowFunction::class . '->attrGroups' => [null, '', ' '],
Expr\Closure::class . '->attrGroups' => [null, '', ' '], Expr\Closure::class . '->attrGroups' => [null, '', ' '],
Stmt\Const_::class . '->attrGroups' => [null, '', "\n"],
PrintableNewAnonClassNode::class . '->attrGroups' => [\T_NEW, ' ', ''], PrintableNewAnonClassNode::class . '->attrGroups' => [\T_NEW, ' ', ''],
/* These cannot be empty to start with: /* These cannot be empty to start with:

View File

@ -1,191 +1,259 @@
parameters: parameters:
ignoreErrors: ignoreErrors:
- -
message: "#^Method PhpParser\\\\Builder\\\\ClassConst\\:\\:__construct\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#" message: '#^Method PhpParser\\Builder\\ClassConst\:\:__construct\(\) has parameter \$value with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1 count: 1
path: lib/PhpParser/Builder/ClassConst.php path: lib/PhpParser/Builder/ClassConst.php
- -
message: "#^Method PhpParser\\\\Builder\\\\ClassConst\\:\\:addConst\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#" message: '#^Method PhpParser\\Builder\\ClassConst\:\:addConst\(\) has parameter \$value with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1 count: 1
path: lib/PhpParser/Builder/ClassConst.php path: lib/PhpParser/Builder/ClassConst.php
- -
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:args\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#" message: '#^Method PhpParser\\BuilderFactory\:\:args\(\) has parameter \$args with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1 count: 1
path: lib/PhpParser/BuilderFactory.php path: lib/PhpParser/BuilderFactory.php
- -
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:attribute\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#" message: '#^Method PhpParser\\BuilderFactory\:\:attribute\(\) has parameter \$args with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1 count: 1
path: lib/PhpParser/BuilderFactory.php path: lib/PhpParser/BuilderFactory.php
- -
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:classConst\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#" message: '#^Method PhpParser\\BuilderFactory\:\:classConst\(\) has parameter \$value with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1 count: 1
path: lib/PhpParser/BuilderFactory.php path: lib/PhpParser/BuilderFactory.php
- -
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:funcCall\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#" message: '#^Method PhpParser\\BuilderFactory\:\:funcCall\(\) has parameter \$args with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1 count: 1
path: lib/PhpParser/BuilderFactory.php path: lib/PhpParser/BuilderFactory.php
- -
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:methodCall\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#" message: '#^Method PhpParser\\BuilderFactory\:\:methodCall\(\) has parameter \$args with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1 count: 1
path: lib/PhpParser/BuilderFactory.php path: lib/PhpParser/BuilderFactory.php
- -
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:new\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#" message: '#^Method PhpParser\\BuilderFactory\:\:new\(\) has parameter \$args with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1 count: 1
path: lib/PhpParser/BuilderFactory.php path: lib/PhpParser/BuilderFactory.php
- -
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:staticCall\\(\\) has parameter \\$args with no value type specified in iterable type array\\.$#" message: '#^Method PhpParser\\BuilderFactory\:\:staticCall\(\) has parameter \$args with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1 count: 1
path: lib/PhpParser/BuilderFactory.php path: lib/PhpParser/BuilderFactory.php
- -
message: "#^Method PhpParser\\\\BuilderFactory\\:\\:val\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#" message: '#^Method PhpParser\\BuilderFactory\:\:val\(\) has parameter \$value with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1 count: 1
path: lib/PhpParser/BuilderFactory.php path: lib/PhpParser/BuilderFactory.php
- -
message: "#^Method PhpParser\\\\BuilderHelpers\\:\\:normalizeValue\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#" message: '#^Method PhpParser\\BuilderHelpers\:\:normalizeValue\(\) has parameter \$value with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1 count: 1
path: lib/PhpParser/BuilderHelpers.php path: lib/PhpParser/BuilderHelpers.php
- -
message: "#^Method PhpParser\\\\ConstExprEvaluator\\:\\:evaluateArray\\(\\) return type has no value type specified in iterable type array\\.$#" message: '#^Method PhpParser\\ConstExprEvaluator\:\:evaluateArray\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1 count: 1
path: lib/PhpParser/ConstExprEvaluator.php path: lib/PhpParser/ConstExprEvaluator.php
- -
message: "#^Unary operation \"~\" on mixed results in an error\\.$#" message: '#^Method PhpParser\\JsonDecoder\:\:decodeArray\(\) has parameter \$array with no value type specified in iterable type array\.$#'
count: 1 identifier: missingType.iterableValue
path: lib/PhpParser/ConstExprEvaluator.php
-
message: "#^Method PhpParser\\\\JsonDecoder\\:\\:decodeArray\\(\\) has parameter \\$array with no value type specified in iterable type array\\.$#"
count: 1 count: 1
path: lib/PhpParser/JsonDecoder.php path: lib/PhpParser/JsonDecoder.php
- -
message: "#^Method PhpParser\\\\JsonDecoder\\:\\:decodeArray\\(\\) return type has no value type specified in iterable type array\\.$#" message: '#^Method PhpParser\\JsonDecoder\:\:decodeArray\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1 count: 1
path: lib/PhpParser/JsonDecoder.php path: lib/PhpParser/JsonDecoder.php
- -
message: "#^Method PhpParser\\\\JsonDecoder\\:\\:decodeComment\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#" message: '#^Method PhpParser\\JsonDecoder\:\:decodeComment\(\) has parameter \$value with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1 count: 1
path: lib/PhpParser/JsonDecoder.php path: lib/PhpParser/JsonDecoder.php
- -
message: "#^Method PhpParser\\\\JsonDecoder\\:\\:decodeNode\\(\\) has parameter \\$value with no value type specified in iterable type array\\.$#" message: '#^Method PhpParser\\JsonDecoder\:\:decodeNode\(\) has parameter \$value with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1 count: 1
path: lib/PhpParser/JsonDecoder.php path: lib/PhpParser/JsonDecoder.php
- -
message: "#^Call to function assert\\(\\) with false will always evaluate to false\\.$#" message: '#^Call to function assert\(\) with false will always evaluate to false\.$#'
identifier: function.impossibleType
count: 1 count: 1
path: lib/PhpParser/Lexer/Emulative.php path: lib/PhpParser/Lexer/Emulative.php
- -
message: "#^Constant T_PRIVATE_SET not found\\.$#" message: '#^If condition is always false\.$#'
count: 2 identifier: if.alwaysFalse
path: lib/PhpParser/Lexer/TokenEmulator/AsymmetricVisibilityTokenEmulator.php
-
message: "#^Constant T_PROTECTED_SET not found\\.$#"
count: 2
path: lib/PhpParser/Lexer/TokenEmulator/AsymmetricVisibilityTokenEmulator.php
-
message: "#^Constant T_PUBLIC_SET not found\\.$#"
count: 2
path: lib/PhpParser/Lexer/TokenEmulator/AsymmetricVisibilityTokenEmulator.php
-
message: "#^Constant T_PROPERTY_C not found\\.$#"
count: 1 count: 1
path: lib/PhpParser/Lexer/TokenEmulator/PropertyTokenEmulator.php path: lib/PhpParser/Node/Expr/ArrayItem.php
- -
message: "#^Method PhpParser\\\\NodeDumper\\:\\:__construct\\(\\) has parameter \\$options with no value type specified in iterable type array\\.$#" message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Expr/ClosureUse.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Scalar/DNumber.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Scalar/Encapsed.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Scalar/EncapsedStringPart.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Scalar/LNumber.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Stmt/DeclareDeclare.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Stmt/PropertyProperty.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Stmt/StaticVar.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: lib/PhpParser/Node/Stmt/UseUse.php
-
message: '#^Method PhpParser\\NodeDumper\:\:__construct\(\) has parameter \$options with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1 count: 1
path: lib/PhpParser/NodeDumper.php path: lib/PhpParser/NodeDumper.php
- -
message: "#^Method PhpParser\\\\NodeDumper\\:\\:dump\\(\\) has parameter \\$node with no value type specified in iterable type array\\.$#" message: '#^Method PhpParser\\NodeDumper\:\:dump\(\) has parameter \$node with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1 count: 1
path: lib/PhpParser/NodeDumper.php path: lib/PhpParser/NodeDumper.php
- -
message: "#^Method PhpParser\\\\NodeTraverser\\:\\:traverseArray\\(\\) has parameter \\$nodes with no value type specified in iterable type array\\.$#" message: '#^Method PhpParser\\NodeTraverser\:\:traverseArray\(\) has parameter \$nodes with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1 count: 1
path: lib/PhpParser/NodeTraverser.php path: lib/PhpParser/NodeTraverser.php
- -
message: "#^Method PhpParser\\\\NodeTraverser\\:\\:traverseArray\\(\\) return type has no value type specified in iterable type array\\.$#" message: '#^Method PhpParser\\NodeTraverser\:\:traverseArray\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1 count: 1
path: lib/PhpParser/NodeTraverser.php path: lib/PhpParser/NodeTraverser.php
- -
message: "#^Access to an undefined property PhpParser\\\\Node\\:\\:\\$attrGroups\\.$#" message: '#^Access to an undefined property PhpParser\\Node\:\:\$attrGroups\.$#'
identifier: property.notFound
count: 1 count: 1
path: lib/PhpParser/NodeVisitor/NameResolver.php path: lib/PhpParser/NodeVisitor/NameResolver.php
- -
message: "#^Access to an undefined property PhpParser\\\\Node\\:\\:\\$name\\.$#" message: '#^Access to an undefined property PhpParser\\Node\:\:\$name\.$#'
identifier: property.notFound
count: 1 count: 1
path: lib/PhpParser/NodeVisitor/NameResolver.php path: lib/PhpParser/NodeVisitor/NameResolver.php
- -
message: "#^Access to an undefined property PhpParser\\\\Node\\:\\:\\$namespacedName\\.$#" message: '#^Access to an undefined property PhpParser\\Node\:\:\$namespacedName\.$#'
identifier: property.notFound
count: 1 count: 1
path: lib/PhpParser/NodeVisitor/NameResolver.php path: lib/PhpParser/NodeVisitor/NameResolver.php
- -
message: "#^Method PhpParser\\\\NodeVisitor\\\\NodeConnectingVisitor\\:\\:beforeTraverse\\(\\) should return array\\<PhpParser\\\\Node\\>\\|null but return statement is missing\\.$#" message: '#^Method PhpParser\\NodeVisitor\\NodeConnectingVisitor\:\:beforeTraverse\(\) should return array\<PhpParser\\Node\>\|null but return statement is missing\.$#'
identifier: return.missing
count: 1 count: 1
path: lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php path: lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php
- -
message: "#^Method PhpParser\\\\NodeVisitor\\\\NodeConnectingVisitor\\:\\:enterNode\\(\\) should return array\\<PhpParser\\\\Node\\>\\|int\\|PhpParser\\\\Node\\|null but return statement is missing\\.$#" message: '#^Method PhpParser\\NodeVisitor\\NodeConnectingVisitor\:\:enterNode\(\) should return array\<PhpParser\\Node\>\|int\|PhpParser\\Node\|null but return statement is missing\.$#'
identifier: return.missing
count: 1 count: 1
path: lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php path: lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php
- -
message: "#^Method PhpParser\\\\NodeVisitor\\\\NodeConnectingVisitor\\:\\:leaveNode\\(\\) should return array\\<PhpParser\\\\Node\\>\\|int\\|PhpParser\\\\Node\\|null but return statement is missing\\.$#" message: '#^Method PhpParser\\NodeVisitor\\NodeConnectingVisitor\:\:leaveNode\(\) should return array\<PhpParser\\Node\>\|int\|PhpParser\\Node\|null but return statement is missing\.$#'
identifier: return.missing
count: 1 count: 1
path: lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php path: lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php
- -
message: "#^Method PhpParser\\\\NodeVisitor\\\\ParentConnectingVisitor\\:\\:beforeTraverse\\(\\) should return array\\<PhpParser\\\\Node\\>\\|null but return statement is missing\\.$#" message: '#^Method PhpParser\\NodeVisitor\\ParentConnectingVisitor\:\:beforeTraverse\(\) should return array\<PhpParser\\Node\>\|null but return statement is missing\.$#'
identifier: return.missing
count: 1 count: 1
path: lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php path: lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php
- -
message: "#^Method PhpParser\\\\NodeVisitor\\\\ParentConnectingVisitor\\:\\:enterNode\\(\\) should return array\\<PhpParser\\\\Node\\>\\|int\\|PhpParser\\\\Node\\|null but return statement is missing\\.$#" message: '#^Method PhpParser\\NodeVisitor\\ParentConnectingVisitor\:\:enterNode\(\) should return array\<PhpParser\\Node\>\|int\|PhpParser\\Node\|null but return statement is missing\.$#'
identifier: return.missing
count: 1 count: 1
path: lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php path: lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php
- -
message: "#^Method PhpParser\\\\NodeVisitor\\\\ParentConnectingVisitor\\:\\:leaveNode\\(\\) should return array\\<PhpParser\\\\Node\\>\\|int\\|PhpParser\\\\Node\\|null but return statement is missing\\.$#" message: '#^Method PhpParser\\NodeVisitor\\ParentConnectingVisitor\:\:leaveNode\(\) should return array\<PhpParser\\Node\>\|int\|PhpParser\\Node\|null but return statement is missing\.$#'
identifier: return.missing
count: 1 count: 1
path: lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php path: lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php
- -
message: "#^Access to undefined constant static\\(PhpParser\\\\ParserAbstract\\)\\:\\:T_ECHO\\.$#" message: '#^Access to undefined constant static\(PhpParser\\ParserAbstract\)\:\:T_ECHO\.$#'
identifier: classConstant.notFound
count: 1 count: 1
path: lib/PhpParser/ParserAbstract.php path: lib/PhpParser/ParserAbstract.php
- -
message: "#^Unary operation \"\\+\" on string results in an error\\.$#" message: '#^Unary operation "\+" on string results in an error\.$#'
identifier: unaryOp.invalid
count: 1 count: 1
path: lib/PhpParser/ParserAbstract.php path: lib/PhpParser/ParserAbstract.php
- -
message: "#^Variable \\$action might not be defined\\.$#" message: '#^Variable \$action might not be defined\.$#'
identifier: variable.undefined
count: 1 count: 1
path: lib/PhpParser/ParserAbstract.php path: lib/PhpParser/ParserAbstract.php

View File

@ -365,7 +365,10 @@ abstract class SomeClass extends SomeOtherClass implements A\Few, \Interfaces
* @param SomeClass And takes a parameter * @param SomeClass And takes a parameter
*/ */
abstract public function someMethod(SomeClass $someParam); abstract public function someMethod(SomeClass $someParam);
protected function anotherMethod(#[TaggedIterator('app.handlers')] $someParam = 'test') protected function anotherMethod(
#[TaggedIterator('app.handlers')]
$someParam = 'test'
)
{ {
print $someParam; print $someParam;
} }

View File

@ -24,7 +24,7 @@ class CodeTestParser {
// multiple sections possible with always two forming a pair // multiple sections possible with always two forming a pair
$chunks = array_chunk($parts, $chunksPerTest); $chunks = array_chunk($parts, $chunksPerTest);
$tests = []; $tests = [];
foreach ($chunks as $i => $chunk) { foreach ($chunks as $chunk) {
$lastPart = array_pop($chunk); $lastPart = array_pop($chunk);
list($lastPart, $mode) = $this->extractMode($lastPart); list($lastPart, $mode) = $this->extractMode($lastPart);
$tests[] = [$mode, array_merge($chunk, [$lastPart])]; $tests[] = [$mode, array_merge($chunk, [$lastPart])];
@ -61,7 +61,7 @@ class CodeTestParser {
return [$expected, null]; return [$expected, null];
} }
$expected = (string) substr($expected, $firstNewLine + 1); $expected = substr($expected, $firstNewLine + 1);
return [$expected, substr($firstLine, 2)]; return [$expected, substr($firstLine, 2)];
} }
} }

View File

@ -47,4 +47,18 @@ class ParamTest extends \PHPUnit\Framework\TestCase {
$node->flags = Modifiers::PUBLIC_SET; $node->flags = Modifiers::PUBLIC_SET;
$this->assertTrue($node->isPublicSet()); $this->assertTrue($node->isPublicSet());
} }
public function testPromotedPropertyWithoutVisibilityModifier(): void {
$node = new Param(new Variable('foo'));
$get = new PropertyHook('get', null);
$node->hooks[] = $get;
$this->assertTrue($node->isPromoted());
$this->assertTrue($node->isPublic());
}
public function testNonPromotedPropertyIsNotPublic(): void {
$node = new Param(new Variable('foo'));
$this->assertFalse($node->isPublic());
}
} }

View File

@ -0,0 +1,89 @@
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\Modifiers;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Scalar\Int_;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Return_;
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter\Standard;
class PropertyHookTest extends \PHPUnit\Framework\TestCase {
/**
* @dataProvider provideModifiers
*/
public function testModifiers($modifier): void {
$node = new PropertyHook(
'get',
null,
[
'flags' => constant(Modifiers::class . '::' . strtoupper($modifier)),
]
);
$this->assertTrue($node->{'is' . $modifier}());
}
public function testNoModifiers(): void {
$node = new PropertyHook('get', null);
$this->assertFalse($node->isFinal());
}
public static function provideModifiers() {
return [
['final'],
];
}
public function testGetStmts(): void {
$expr = new Variable('test');
$get = new PropertyHook('get', $expr);
$this->assertEquals([new Return_($expr)], $get->getStmts());
$set = new PropertyHook('set', $expr, [], ['propertyName' => 'abc']);
$this->assertEquals([
new Expression(new Assign(new PropertyFetch(new Variable('this'), 'abc'), $expr))
], $set->getStmts());
}
public function testGetStmtsSetHookFromParser(): void {
$parser = (new ParserFactory())->createForNewestSupportedVersion();
$prettyPrinter = new Standard();
$stmts = $parser->parse(<<<'CODE'
<?php
class Test {
public $prop1 { set => 123; }
public function __construct(public $prop2 { set => 456; }) {}
}
CODE);
$hook1 = $stmts[0]->stmts[0]->hooks[0];
$this->assertEquals('$this->prop1 = 123;', $prettyPrinter->prettyPrint($hook1->getStmts()));
$hook2 = $stmts[0]->stmts[1]->params[0]->hooks[0];
$this->assertEquals('$this->prop2 = 456;', $prettyPrinter->prettyPrint($hook2->getStmts()));
}
public function testGetStmtsUnknownHook(): void {
$expr = new Variable('test');
$hook = new PropertyHook('foobar', $expr);
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Unknown property hook "foobar"');
$hook->getStmts();
}
public function testGetStmtsSetHookWithoutPropertyName(): void {
$expr = new Variable('test');
$set = new PropertyHook('set', $expr);
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Can only use getStmts() on a "set" hook if the "propertyName" attribute is set');
$set->getStmts();
}
}

View File

@ -15,11 +15,11 @@ class DNumberTest extends \PHPUnit\Framework\TestCase {
$this->assertInstanceOf(Echo_::class, $echo); $this->assertInstanceOf(Echo_::class, $echo);
/** @var Echo_ $echo */ /** @var Echo_ $echo */
$lLumber = $echo->exprs[0]; $dnumber = $echo->exprs[0];
$this->assertInstanceOf(Float_::class, $lLumber); $this->assertInstanceOf(Float_::class, $dnumber);
/** @var Float_ $dnumber */ /** @var Float_ $dnumber */
$this->assertSame(1234.56, $lLumber->value); $this->assertSame(1234.56, $dnumber->value);
$this->assertSame('1_234.56', $lLumber->getAttribute('rawValue')); $this->assertSame('1_234.56', $dnumber->getAttribute('rawValue'));
} }
} }

View File

@ -14,11 +14,11 @@ class NumberTest extends \PHPUnit\Framework\TestCase {
$this->assertInstanceOf(Echo_::class, $echo); $this->assertInstanceOf(Echo_::class, $echo);
/** @var Echo_ $echo */ /** @var Echo_ $echo */
$lLumber = $echo->exprs[0]; $lnumber = $echo->exprs[0];
$this->assertInstanceOf(Int_::class, $lLumber); $this->assertInstanceOf(Int_::class, $lnumber);
/** @var Int_ $lnumber */ /** @var Int_ $lnumber */
$this->assertSame(1234, $lLumber->value); $this->assertSame(1234, $lnumber->value);
$this->assertSame('1_234', $lLumber->getAttribute('rawValue')); $this->assertSame('1_234', $lnumber->getAttribute('rawValue'));
} }
} }

View File

@ -57,4 +57,14 @@ class PropertyTest extends \PHPUnit\Framework\TestCase {
$node = new Property(Modifiers::PUBLIC_SET, []); $node = new Property(Modifiers::PUBLIC_SET, []);
$this->assertTrue($node->isPublicSet()); $this->assertTrue($node->isPublicSet());
} }
public function testIsFinal() {
$node = new Property(Modifiers::FINAL, []);
$this->assertTrue($node->isFinal());
}
public function testIsAbstract() {
$node = new Property(Modifiers::ABSTRACT, []);
$this->assertTrue($node->isAbstract());
}
} }

View File

@ -245,6 +245,9 @@ fn(array $a): array => $a;
fn(A $a): A => $a; fn(A $a): A => $a;
fn(?A $a): ?A => $a; fn(?A $a): ?A => $a;
#[X]
const EXAMPLE = true;
A::b(); A::b();
A::$b; A::$b;
A::B; A::B;
@ -283,12 +286,18 @@ class A extends \NS\B implements \NS\C, \NS\D
public const \X\Foo C = \X\Foo::Bar; public const \X\Foo C = \X\Foo::Bar;
public \NS\Foo $foo { public \NS\Foo $foo {
#[\NS\X] #[\NS\X]
set(#[\NS\X] \NS\Bar $v) { set(
#[\NS\X]
\NS\Bar $v
) {
} }
} }
public function __construct(public \NS\Foo $bar { public function __construct(public \NS\Foo $bar {
#[\NS\X] #[\NS\X]
set(#[\NS\X] \NS\Bar $v) { set(
#[\NS\X]
\NS\Bar $v
) {
} }
}) })
{ {
@ -312,7 +321,10 @@ trait A
{ {
} }
#[\NS\X] #[\NS\X]
function f(#[\NS\X] \NS\A $a): \NS\A function f(
#[\NS\X]
\NS\A $a
): \NS\A
{ {
} }
function f2(array $a): array function f2(array $a): array
@ -329,6 +341,8 @@ function fn4(?array $a): ?array
#[\NS\X] fn(array $a): array => $a; #[\NS\X] fn(array $a): array => $a;
fn(\NS\A $a): \NS\A => $a; fn(\NS\A $a): \NS\A => $a;
fn(?\NS\A $a): ?\NS\A => $a; fn(?\NS\A $a): ?\NS\A => $a;
#[\NS\X]
const EXAMPLE = true;
\NS\A::b(); \NS\A::b();
\NS\A::$b; \NS\A::$b;
\NS\A::B; \NS\A::B;

View File

@ -30,4 +30,28 @@ final class NodeConnectingVisitorTest extends \PHPUnit\Framework\TestCase {
$this->assertSame(Else_::class, get_class($node->getAttribute('next'))); $this->assertSame(Else_::class, get_class($node->getAttribute('next')));
} }
public function testWeakReferences(): void {
$ast = (new ParserFactory())->createForNewestSupportedVersion()->parse(
'<?php if (true) {} else {}'
);
$traverser = new NodeTraverser();
$traverser->addVisitor(new NodeConnectingVisitor(true));
$ast = $traverser->traverse($ast);
$node = (new NodeFinder())->findFirstInstanceof($ast, Else_::class);
$this->assertInstanceOf(\WeakReference::class, $node->getAttribute('weak_parent'));
$this->assertSame(If_::class, get_class($node->getAttribute('weak_parent')->get()));
$this->assertInstanceOf(\WeakReference::class, $node->getAttribute('weak_previous'));
$this->assertSame(ConstFetch::class, get_class($node->getAttribute('weak_previous')->get()));
$node = (new NodeFinder())->findFirstInstanceof($ast, ConstFetch::class);
$this->assertInstanceOf(\WeakReference::class, $node->getAttribute('weak_next'));
$this->assertSame(Else_::class, get_class($node->getAttribute('weak_next')->get()));
}
} }

View File

@ -23,4 +23,22 @@ final class ParentConnectingVisitorTest extends \PHPUnit\Framework\TestCase {
$this->assertSame('C', $node->getAttribute('parent')->name->toString()); $this->assertSame('C', $node->getAttribute('parent')->name->toString());
} }
public function testWeakReferences(): void {
$ast = (new ParserFactory())->createForNewestSupportedVersion()->parse(
'<?php class C { public function m() {} }'
);
$traverser = new NodeTraverser();
$traverser->addVisitor(new ParentConnectingVisitor(true));
$ast = $traverser->traverse($ast);
$node = (new NodeFinder())->findFirstInstanceof($ast, ClassMethod::class);
$weakReference = $node->getAttribute('weak_parent');
$this->assertInstanceOf(\WeakReference::class, $weakReference);
$this->assertSame('C', $weakReference->get()->name->toString());
}
} }

View File

@ -0,0 +1,50 @@
Constants
-----
<?php
const FOO = true;
#[A]
const BAR = true;
-----
$attrGroup = new Node\AttributeGroup([
new Node\Attribute(new Node\Name('B'), []),
]);
$stmts[0]->attrGroups[] = $attrGroup;
$stmts[1]->attrGroups[] = $attrGroup;
-----
<?php
#[B]
const FOO = true;
#[A]
#[B]
const BAR = true;
-----
<?php
#[ A, B]
const FOO = true;
#[
A,
B,
]
const BAR = true;
-----
$attr = new Node\Attribute(new Node\Name('C'), []);
$stmts[0]->attrGroups[0]->attrs[] = $attr;
$stmts[1]->attrGroups[0]->attrs[] = $attr;
-----
<?php
#[ A, B, C]
const FOO = true;
#[
A,
B,
C,
]
const BAR = true;

View File

@ -476,6 +476,8 @@ array(
) )
) )
3: Stmt_Const( 3: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -629,6 +631,8 @@ array(
) )
) )
3: Stmt_Const( 3: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(

View File

@ -42,6 +42,8 @@ const T_35 = !false;
----- -----
array( array(
0: Stmt_Const( 0: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -59,6 +61,8 @@ array(
) )
) )
1: Stmt_Const( 1: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -76,6 +80,8 @@ array(
) )
) )
2: Stmt_Const( 2: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -93,6 +99,8 @@ array(
) )
) )
3: Stmt_Const( 3: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -110,6 +118,8 @@ array(
) )
) )
4: Stmt_Const( 4: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -132,6 +142,8 @@ array(
) )
) )
5: Stmt_Const( 5: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -159,6 +171,8 @@ array(
) )
) )
6: Stmt_Const( 6: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -170,6 +184,8 @@ array(
) )
) )
7: Stmt_Const( 7: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -182,6 +198,8 @@ array(
) )
) )
8: Stmt_Const( 8: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -198,6 +216,8 @@ array(
) )
) )
9: Stmt_Const( 9: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -231,6 +251,8 @@ array(
) )
) )
10: Stmt_Const( 10: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -248,6 +270,8 @@ array(
) )
) )
11: Stmt_Const( 11: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -265,6 +289,8 @@ array(
) )
) )
12: Stmt_Const( 12: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -282,6 +308,8 @@ array(
) )
) )
13: Stmt_Const( 13: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -299,6 +327,8 @@ array(
) )
) )
14: Stmt_Const( 14: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -316,6 +346,8 @@ array(
) )
) )
15: Stmt_Const( 15: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -333,6 +365,8 @@ array(
) )
) )
16: Stmt_Const( 16: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -350,6 +384,8 @@ array(
) )
) )
17: Stmt_Const( 17: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -367,6 +403,8 @@ array(
) )
) )
18: Stmt_Const( 18: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -384,6 +422,8 @@ array(
) )
) )
19: Stmt_Const( 19: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -401,6 +441,8 @@ array(
) )
) )
20: Stmt_Const( 20: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -418,6 +460,8 @@ array(
) )
) )
21: Stmt_Const( 21: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -435,6 +479,8 @@ array(
) )
) )
22: Stmt_Const( 22: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -452,6 +498,8 @@ array(
) )
) )
23: Stmt_Const( 23: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -469,6 +517,8 @@ array(
) )
) )
24: Stmt_Const( 24: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -491,6 +541,8 @@ array(
) )
) )
25: Stmt_Const( 25: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -513,6 +565,8 @@ array(
) )
) )
26: Stmt_Const( 26: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -530,6 +584,8 @@ array(
) )
) )
27: Stmt_Const( 27: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -572,6 +628,8 @@ array(
) )
) )
28: Stmt_Const( 28: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -589,6 +647,8 @@ array(
) )
) )
29: Stmt_Const( 29: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -606,6 +666,8 @@ array(
) )
) )
30: Stmt_Const( 30: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -623,6 +685,8 @@ array(
) )
) )
31: Stmt_Const( 31: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -640,6 +704,8 @@ array(
) )
) )
32: Stmt_Const( 32: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -657,6 +723,8 @@ array(
) )
) )
33: Stmt_Const( 33: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -674,6 +742,8 @@ array(
) )
) )
34: Stmt_Const( 34: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(

View File

@ -523,3 +523,116 @@ array(
) )
) )
) )
-----
<?php
class Test
{
public $foo, $bar { get { return 42; } }
}
-----
Cannot use hooks when declaring multiple properties from 5:23 to 5:23
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Test
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: PUBLIC (1)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: foo
)
default: null
)
1: PropertyItem(
name: VarLikeIdentifier(
name: bar
)
default: null
)
)
hooks: array(
0: PropertyHook(
attrGroups: array(
)
flags: 0
byRef: false
name: Identifier(
name: get
)
params: array(
)
body: array(
0: Stmt_Return(
expr: Scalar_Int(
value: 42
)
)
)
)
)
)
)
)
)
-----
<?php
class Test
{
public $foo, $bar { }
}
-----
Cannot use hooks when declaring multiple properties from 5:23 to 5:23
Property hook list cannot be empty from 5:23 to 5:23
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: Test
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: PUBLIC (1)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: foo
)
default: null
)
1: PropertyItem(
name: VarLikeIdentifier(
name: bar
)
default: null
)
)
hooks: array(
)
)
)
)
)

View File

@ -3,9 +3,26 @@ Global constants
<?php <?php
const A = 0, B = 1.0, C = 'A', D = E; const A = 0, B = 1.0, C = 'A', D = E;
#[Example]
const WithOneAttribute = 1;
#[First]
#[Second]
const WithUngroupedAttriutes = 2;
#[First, Second]
const WithGroupAttributes = 3;
#[Example]
const ThisIsInvalid = 4,
AttributesOnMultipleConstants = 5;
----- -----
Cannot use attributes on multiple constants at once from 15:1 to 17:38
array( array(
0: Stmt_Const( 0: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(
@ -43,4 +60,130 @@ array(
) )
) )
) )
1: Stmt_Const(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
name: Example
)
args: array(
)
)
)
)
)
consts: array(
0: Const(
name: Identifier(
name: WithOneAttribute
)
value: Scalar_Int(
value: 1
)
)
)
)
2: Stmt_Const(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
name: First
)
args: array(
)
)
)
)
1: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
name: Second
)
args: array(
)
)
)
)
)
consts: array(
0: Const(
name: Identifier(
name: WithUngroupedAttriutes
)
value: Scalar_Int(
value: 2
)
)
)
)
3: Stmt_Const(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
name: First
)
args: array(
)
)
1: Attribute(
name: Name(
name: Second
)
args: array(
)
)
)
)
)
consts: array(
0: Const(
name: Identifier(
name: WithGroupAttributes
)
value: Scalar_Int(
value: 3
)
)
)
)
4: Stmt_Const(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
name: Example
)
args: array(
)
)
)
)
)
consts: array(
0: Const(
name: Identifier(
name: ThisIsInvalid
)
value: Scalar_Int(
value: 4
)
)
1: Const(
name: Identifier(
name: AttributesOnMultipleConstants
)
value: Scalar_Int(
value: 5
)
)
)
)
) )

View File

@ -0,0 +1,448 @@
Declaring clone function stub
-----
<?php
function clone(object $object, array $withProperties = []): object {}
clone $x;
clone($x);
clone($x, );
clone($x, [ "foo" => $foo, "bar" => $bar ]);
clone($x, $array);
clone($x, $array, $extraParameter, $trailingComma, );
clone(object: $x, withProperties: [ "foo" => $foo, "bar" => $bar ]);
clone($x, withProperties: [ "foo" => $foo, "bar" => $bar ]);
clone(object: $x);
clone(object: $x, [ "foo" => $foo, "bar" => $bar ]);
clone(...["object" => $x, "withProperties" => [ "foo" => $foo, "bar" => $bar ]]);
clone(...);
-----
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: clone
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: Identifier(
name: object
)
byRef: false
variadic: false
var: Expr_Variable(
name: object
)
default: null
hooks: array(
)
)
1: Param(
attrGroups: array(
)
flags: 0
type: Identifier(
name: array
)
byRef: false
variadic: false
var: Expr_Variable(
name: withProperties
)
default: Expr_Array(
items: array(
)
)
hooks: array(
)
)
)
returnType: Identifier(
name: object
)
stmts: array(
)
)
1: Stmt_Expression(
expr: Expr_Clone(
expr: Expr_Variable(
name: x
)
)
)
2: Stmt_Expression(
expr: Expr_Clone(
expr: Expr_Variable(
name: x
)
)
)
3: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
name: clone
)
args: array(
0: Arg(
name: null
value: Expr_Variable(
name: x
)
byRef: false
unpack: false
)
)
)
)
4: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
name: clone
)
args: array(
0: Arg(
name: null
value: Expr_Variable(
name: x
)
byRef: false
unpack: false
)
1: Arg(
name: null
value: Expr_Array(
items: array(
0: ArrayItem(
key: Scalar_String(
value: foo
)
value: Expr_Variable(
name: foo
)
byRef: false
unpack: false
)
1: ArrayItem(
key: Scalar_String(
value: bar
)
value: Expr_Variable(
name: bar
)
byRef: false
unpack: false
)
)
)
byRef: false
unpack: false
)
)
)
)
5: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
name: clone
)
args: array(
0: Arg(
name: null
value: Expr_Variable(
name: x
)
byRef: false
unpack: false
)
1: Arg(
name: null
value: Expr_Variable(
name: array
)
byRef: false
unpack: false
)
)
)
)
6: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
name: clone
)
args: array(
0: Arg(
name: null
value: Expr_Variable(
name: x
)
byRef: false
unpack: false
)
1: Arg(
name: null
value: Expr_Variable(
name: array
)
byRef: false
unpack: false
)
2: Arg(
name: null
value: Expr_Variable(
name: extraParameter
)
byRef: false
unpack: false
)
3: Arg(
name: null
value: Expr_Variable(
name: trailingComma
)
byRef: false
unpack: false
)
)
)
)
7: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
name: clone
)
args: array(
0: Arg(
name: Identifier(
name: object
)
value: Expr_Variable(
name: x
)
byRef: false
unpack: false
)
1: Arg(
name: Identifier(
name: withProperties
)
value: Expr_Array(
items: array(
0: ArrayItem(
key: Scalar_String(
value: foo
)
value: Expr_Variable(
name: foo
)
byRef: false
unpack: false
)
1: ArrayItem(
key: Scalar_String(
value: bar
)
value: Expr_Variable(
name: bar
)
byRef: false
unpack: false
)
)
)
byRef: false
unpack: false
)
)
)
)
8: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
name: clone
)
args: array(
0: Arg(
name: null
value: Expr_Variable(
name: x
)
byRef: false
unpack: false
)
1: Arg(
name: Identifier(
name: withProperties
)
value: Expr_Array(
items: array(
0: ArrayItem(
key: Scalar_String(
value: foo
)
value: Expr_Variable(
name: foo
)
byRef: false
unpack: false
)
1: ArrayItem(
key: Scalar_String(
value: bar
)
value: Expr_Variable(
name: bar
)
byRef: false
unpack: false
)
)
)
byRef: false
unpack: false
)
)
)
)
9: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
name: clone
)
args: array(
0: Arg(
name: Identifier(
name: object
)
value: Expr_Variable(
name: x
)
byRef: false
unpack: false
)
)
)
)
10: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
name: clone
)
args: array(
0: Arg(
name: Identifier(
name: object
)
value: Expr_Variable(
name: x
)
byRef: false
unpack: false
)
1: Arg(
name: null
value: Expr_Array(
items: array(
0: ArrayItem(
key: Scalar_String(
value: foo
)
value: Expr_Variable(
name: foo
)
byRef: false
unpack: false
)
1: ArrayItem(
key: Scalar_String(
value: bar
)
value: Expr_Variable(
name: bar
)
byRef: false
unpack: false
)
)
)
byRef: false
unpack: false
)
)
)
)
11: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
name: clone
)
args: array(
0: Arg(
name: null
value: Expr_Array(
items: array(
0: ArrayItem(
key: Scalar_String(
value: object
)
value: Expr_Variable(
name: x
)
byRef: false
unpack: false
)
1: ArrayItem(
key: Scalar_String(
value: withProperties
)
value: Expr_Array(
items: array(
0: ArrayItem(
key: Scalar_String(
value: foo
)
value: Expr_Variable(
name: foo
)
byRef: false
unpack: false
)
1: ArrayItem(
key: Scalar_String(
value: bar
)
value: Expr_Variable(
name: bar
)
byRef: false
unpack: false
)
)
)
byRef: false
unpack: false
)
)
)
byRef: false
unpack: true
)
)
)
)
12: Stmt_Expression(
expr: Expr_FuncCall(
name: Name(
name: clone
)
args: array(
0: VariadicPlaceholder(
)
)
)
)
)

View File

@ -16,6 +16,8 @@ class Bar {
----- -----
array( array(
0: Stmt_Const( 0: Stmt_Const(
attrGroups: array(
)
consts: array( consts: array(
0: Const( 0: Const(
name: Identifier( name: Identifier(

View File

@ -38,7 +38,10 @@ function a()
class C class C
{ {
#[A6] #[A6]
public function m(#[A7] $param) public function m(
#[A7]
$param
)
{ {
} }
#[A12] #[A12]
@ -58,3 +61,11 @@ $y = #[A11] fn() => 0;
new #[A13] class new #[A13] class
{ {
}; };
-----
<?php
function test(#[A] $b) {}
-----
!!version=8.0
function test(#[A] $b)
{
}

View File

@ -5,7 +5,24 @@ Constant declarations
const FOO = 'BAR'; const FOO = 'BAR';
const FOO = 1 + 1; const FOO = 1 + 1;
const FOO = BAR, BAR = FOO; const FOO = BAR, BAR = FOO;
#[Example]
const FOO = true;
#[First]
#[Second]
const FOO = true;
#[First, Second]
const FOO = true;
----- -----
const FOO = 'BAR'; const FOO = 'BAR';
const FOO = 1 + 1; const FOO = 1 + 1;
const FOO = BAR, BAR = FOO; const FOO = BAR, BAR = FOO;
#[Example]
const FOO = true;
#[First]
#[Second]
const FOO = true;
#[First, Second]
const FOO = true;

View File

@ -1,6 +1,6 @@
{ {
"require": { "require": {
"friendsofphp/php-cs-fixer": "^3.10", "friendsofphp/php-cs-fixer": "^3.10",
"phpstan/phpstan": "^1.8" "phpstan/phpstan": "^2.0"
} }
} }

View File

@ -8,7 +8,7 @@ require $testDir . '/PhpParser/CodeParsingTest.php';
$inputDirs = [$testDir . '/code/parser', $testDir . '/code/prettyPrinter']; $inputDirs = [$testDir . '/code/parser', $testDir . '/code/prettyPrinter'];
if ($argc < 2) { if ($argc < 2) {
echo "Usage: php generateCorpus.php dir/"; echo "Usage: php generateCorpus.php dir/\n";
exit(1); exit(1);
} }

View File

@ -104,7 +104,7 @@ $fuzzer->setTarget(function(string $input) use($lexer, $parser, $prettyPrinter,
$stmts = $parser->parse($input); $stmts = $parser->parse($input);
$printed = $prettyPrinter->prettyPrintFile($stmts); $printed = $prettyPrinter->prettyPrintFile($stmts);
$visitor->setTokens($lexer->getTokens()); $visitor->setTokens($parser->getTokens());
$stmts = $traverser->traverse($stmts); $stmts = $traverser->traverse($stmts);
if ($visitor->hasProblematicConstruct) { if ($visitor->hasProblematicConstruct) {
return; return;
@ -116,7 +116,7 @@ $fuzzer->setTarget(function(string $input) use($lexer, $parser, $prettyPrinter,
throw new Error("Failed to parse pretty printer output"); throw new Error("Failed to parse pretty printer output");
} }
$visitor->setTokens($lexer->getTokens()); $visitor->setTokens($parser->getTokens());
$printedStmts = $traverser->traverse($printedStmts); $printedStmts = $traverser->traverse($printedStmts);
$same = $nodeDumper->dump($stmts) == $nodeDumper->dump($printedStmts); $same = $nodeDumper->dump($stmts) == $nodeDumper->dump($printedStmts);
if (!$same && !preg_match('/<\?php<\?php/i', $input)) { if (!$same && !preg_match('/<\?php<\?php/i', $input)) {