1
0
mirror of https://github.com/nikic/PHP-Parser.git synced 2025-07-22 23:01:26 +02:00

Compare commits

...

25 Commits

Author SHA1 Message Date
Nikita Popov
255000ad49 Release PHP-Parser 5.0.0rc1 2023-12-20 22:32:29 +01:00
Nikita Popov
f7d484aa0e Fix handling of empty input
Fixes .
2023-12-20 21:51:46 +01:00
Nikita Popov
f82a6365a5 Add upgrading nodes for changes since beta1
Fixes .
Fixes .
2023-12-20 21:36:53 +01:00
Ondřej Mirtes
13a41f05a7 Fix typos in UPGRADE-5.0.md 2023-12-17 21:03:53 +01:00
Ondrej Mirtes
f66650073c Fix NameResolver for class constant native type 2023-11-28 20:10:53 +01:00
Nikita Popov
d0b35126e7 Update CHANGELOG 2023-11-12 17:06:58 +01:00
Nikita Popov
3640d18b87 Remove ParserFactory::create()
Don't try to keep backwards-compatibility with the old factory
style, which doesn't map cleanly onto supported options (we only
have ONLY_PHP7/PREFER_PHP7, which should probably create a Php8
parser in terms of how they are used, but this would no longer
match their names).

Instead, I have backported the new createForNewestSupportedVersion()
and createForHostVersion() methods to PHP-Parser 4.
2023-11-12 16:52:30 +01:00
Théo FIDRY
b54302f363 build: Exclude grammar from export git artifact
(cherry picked from commit 402b6cf345)
2023-11-12 13:04:04 +01:00
Nikita Popov
acfccd9d74 Run with updated php-cs-fixer 2023-11-01 18:50:25 +01:00
Sandro Gehri
eabae1f7fa Fix typo 2023-11-01 18:45:07 +01:00
xjaja
8d50e9d066 Don't drop class statements before error ()
When encountering a null statement (indicating that an error occurred),
retain the preceding statements. These were accidentally dropped
previously.

(cherry picked from commit 54103d8387)
2023-10-07 12:22:01 +02:00
Abdul Malik Ikhsan
481fec47f4 Improve performance of find() and findFirst() when passed $nodes is empty array 2023-10-07 12:11:47 +02:00
Nikita Popov
4e27a17cd8 Use visitor to assign comments
This fixes the long-standing issue where a comment would get assigned
to all nodes with the same starting position, instead of only the
outer-most one.

Fixes .
2023-09-28 21:45:08 +02:00
Nikita Popov
de84f76766 Avoid by-reference iteration in NodeTraverser
No need to create reference wrappers around every element in the
array.
2023-09-26 20:25:37 +02:00
Nikita Popov
b4183c2b09 Remove Stmt\Throw
This was a backwards-compatibility shim for Expr\Throw.
2023-09-25 18:37:07 +02:00
Nikita Popov
a1ccf57727 Introduce Stmt\Block
Stmt\Block will be created for { $a; } style blocks, unless these
occur directly inside some structure that is usually combined
with a block.

For example if ($a) { $b; } will continue to use the old
representation (plain array in in If_::$stmts), but a free-standing
{ $b; } will become a Stmt\Block.

Fixes .
2023-09-24 21:13:01 +02:00
Nikita Popov
f5adbb5e3f Add rawValue to InterpolatedStringPart and doc strings
For doc strings, the rawValue (on either the String_ or
InterpolatedStringPrts) does not include the leading indentation
(which is available as docIndentation) or the trailing newline on
the last part.
2023-09-24 12:55:38 +02:00
Nikita Popov
5edc190bda Add support for dumping additional attributes 2023-09-23 21:36:57 +02:00
Nikita Popov
80851163a6 Assemble NodeDumper result in property
Append to a property instead of returning strings. This is
significantly faster when dumping large ASTs.

This also fixes an inconsistency where the indentation level for
strings and comments was off-by-one.
2023-09-23 20:38:53 +02:00
Nikita Popov
8f175fe7c8 Pass --dry-run to php-cs-fixer
Apparently it only returns the correct exit code with the
--dry-run option.
2023-09-23 18:38:52 +02:00
Nikita Popov
d8e8065313 Don't parse unicode escapes for PHP < 7.0
We still had the option for this but were hardcoding it to true.
Make it conditional on the PHP version instead.
2023-09-23 17:35:51 +02:00
RainX
f4961b89ac Add missing phpdoc annotations in AST nodes () 2023-09-18 21:16:54 +02:00
Nikita Popov
8b9488e1e6 Explicit test new ParserFactory methods 2023-09-17 20:42:25 +02:00
Nikita Popov
21fa9c98b3 Drop some @covers annotations 2023-09-17 20:28:02 +02:00
Nikita Popov
0b3c762939 Update docs table of contents 2023-09-17 20:26:35 +02:00
80 changed files with 3088 additions and 2721 deletions
.gitattributes
.github/workflows
.php-cs-fixer.dist.phpCHANGELOG.mdREADME.mdUPGRADE-5.0.md
doc
grammar
lib/PhpParser
phpstan-baseline.neon
test

1
.gitattributes vendored

@@ -1,5 +1,6 @@
/.github export-ignore
/doc export-ignore
/grammar export-ignore
/test export-ignore
/test_old export-ignore
/tools export-ignore

@@ -116,4 +116,4 @@ jobs:
run: |
cd tools && composer install
- name: "php-cs-fixer"
run: "php tools/vendor/bin/php-cs-fixer fix"
run: "php tools/vendor/bin/php-cs-fixer fix --dry-run"

@@ -21,11 +21,6 @@ return $config->setRiskyAllowed(true)
'declare_strict_types' => true,
// Keep argument formatting for now.
'method_argument_space' => ['on_multiline' => 'ignore'],
'binary_operator_spaces' => [
'default' => 'at_least_single_space',
// Work around https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues/7303.
'operators' => ['=' => null],
],
'phpdoc_align' => ['align' => 'left'],
'phpdoc_trim' => true,
'no_empty_phpdoc' => true,

@@ -1,3 +1,35 @@
Version 5.0.0-rc1 (2023-12-20)
------------------------------
See UPGRADE-5.0 for detailed migration instructions.
### Fixed
* Fixed parsing of empty files.
### Added
* Added support for printing additional attributes (like `kind`) in `NodeDumper`.
* Added `rawValue` attribute to `InterpolatedStringPart` and heredoc/nowdoc `String_`s, which
provides the original, unparsed value. It was previously only available for non-interpolated
single/double quoted strings.
* Added `Stmt\Block` to represent `{}` code blocks. Previously, such code blocks were flattened
into the parent statements array. `Stmt\Block` will not be created for structures that are
typically used with code blocks, for example `if ($x) { $y; }` will be represented as previously,
while `if ($x) { { $x; } }` will have an extra `Stmt\Block` wrapper.
### Changed
* Use visitor to assign comments. This fixes the long-standing issue where comments were assigned
to all nodes sharing a starting position. Now only the outer-most node will hold the comments.
* Don't parse unicode escape sequences when targeting PHP < 7.0.
* Improve NodeDumper performance for large dumps.
### Removed
* Removed `Stmt\Throw_` node, use `Expr\Throw_` inside `Stmt\Expression` instead.
* Removed `ParserFactory::create()`.
Version 5.0.0-beta1 (2023-09-17)
--------------------------------

@@ -213,9 +213,8 @@ Component documentation:
* [AST builders](doc/component/AST_builders.markdown)
* Fluent builders for AST nodes
* [Lexer](doc/component/Lexer.markdown)
* Lexer options
* Token and file positions for nodes
* Custom attributes
* Emulation
* Tokens, positions and attributes
* [Error handling](doc/component/Error_handling.markdown)
* Column information for errors
* Error recovery (parsing of syntactically incorrect code)

@@ -13,6 +13,7 @@ In particular, if an older `PhpVersion` is specified, then:
* For versions before PHP 7.0, `$foo =& new Bar()` assignments are allowed without error.
* For versions before PHP 7.0, invalid octal literals `089` are allowed without error.
* For versions before PHP 7.0, unicode escape sequences `\u{123}` in strings are not parsed.
* Type hints are interpreted as a class `Name` or as a built-in `Identifier` depending on PHP
version, for example `int` is treated as a class name on PHP 5.6 and as a built-in on PHP 7.0.
@@ -82,6 +83,149 @@ 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.
### Changes to the block representation
Previously, code blocks `{ ... }` were always flattened into their parent statement list. For
example `while ($x) { $a; { $b; } $c; }` would produce the same node structure as
`if ($x) { $a; $b; $c; }`, namely a `Stmt\While_` node whose `stmts` subnode is an array of four
statements.
Now, the nested `{ $b; }` block is represented using an explicit `Stmt\Block` node. However, the
outer `{ $a; { $b; } $c; }` block is still represented using a simple array in the `stmts` subnode.
```php
# Code
while ($x) { $a; { $b; } $c; }
# Before
Stmt_While(
cond: Expr_Variable(
name: x
)
stmts: array(
0: Stmt_Expression(
expr: Expr_Variable(
name: a
)
)
1: Stmt_Expression(
expr: Expr_Variable(
name: b
)
)
2: Stmt_Expression(
expr: Expr_Variable(
name: c
)
)
)
)
# After
Stmt_While(
cond: Expr_Variable(
name: x
)
stmts: array(
0: Stmt_Expression(
expr: Expr_Variable(
name: a
)
)
1: Stmt_Block(
stmts: array(
0: Stmt_Expression(
expr: Expr_Variable(
name: b
)
)
)
)
2: Stmt_Expression(
expr: Expr_Variable(
name: c
)
)
)
)
```
### Changes to the throw representation
Previously, `throw` statements like `throw $e;` were represented using the `Stmt\Throw_` class,
while uses inside other expressions (such as `$x ?? throw $e`) used the `Expr\Throw_` class.
Now, `throw $e;` is represented as a `Stmt\Expression` that contains an `Expr\Throw_`. The
`Stmt\Throw_` class has been removed.
```php
# Code
throw $e;
# Before
Stmt_Throw(
expr: Expr_Variable(
name: e
)
)
# After
Stmt_Expression(
expr: Expr_Throw(
expr: Expr_Variable(
name: e
)
)
)
```
### Changes to comment assignment
Previously, comments were assigned to all nodes starting at the same position. Now they will be
assigned to the outer-most node only.
```php
# Code
// Comment
$a + $b;
# Before
Stmt_Expression(
expr: Expr_BinaryOp_Plus(
left: Expr_Variable(
name: a
comments: array(
0: // Comment
)
)
right: Expr_Variable(
name: b
)
comments: array(
0: // Comment
)
)
comments: array(
0: // Comment
)
)
# After
Stmt_Expression(
expr: Expr_BinaryOp_Plus(
left: Expr_Variable(
name: a
)
right: Expr_Variable(
name: b
)
)
comments: array(
0: // Comment
)
)
```
### Renamed nodes
A number of AST nodes have been renamed or moved in the AST hierarchy:
@@ -301,7 +445,7 @@ Additionally, the token array is now terminated by a sentinel token with ID 0.
The lexer API is reduced to a single `Lexer::tokenize()` method, which returns an array of tokens. The `startLexing()` and `getNextToken()` methods have been removed.
Responsibility for determining start and end attributes for nodes has been moved from the lexer to the parser. The lexer no longer accepts an options array. The `usedAttributes` option has been removed without replacement, and the parser will now unconditionally add the `comments`, `startLine`, `endLine`, `startFilePos`, `startEndPos`, `startTokenPos` and `startEndPos` attributes.
Responsibility for determining start and end attributes for nodes has been moved from the lexer to the parser. The lexer no longer accepts an options array. The `usedAttributes` option has been removed without replacement, and the parser will now unconditionally add the `comments`, `startLine`, `endLine`, `startFilePos`, `endFilePos`, `startTokenPos` and `endTokenPos` attributes.
There should no longer be a need to directly interact with the `Lexer` for end users, as the `ParserFactory` will create an appropriate instance, and no additional configuration of the lexer is necessary. To use formatting-preserving pretty printing, the setup boilerplate changes as follows:

@@ -27,9 +27,8 @@ Component documentation
* [AST builders](component/AST_builders.markdown)
* Fluent builders for AST nodes
* [Lexer](component/Lexer.markdown)
* Lexer options
* Token and file positions for nodes
* Custom attributes
* Emulation
* Tokens, positions and attributes
* [Error handling](component/Error_handling.markdown)
* Column information for errors
* Error recovery (parsing of syntactically incorrect code)

@@ -97,5 +97,5 @@ If you make use of the name resolution functionality, you will likely want to di
the AST and causing spurious changes to the pretty printed code. For more information, see the
[name resolution documentation](Name_resolution.markdown).
The formatting-preservation works on a best-effort basis and may sometimes reformat more code tha
The formatting-preservation works on a best-effort basis and may sometimes reformat more code than
necessary. If you encounter problems while using this functionality, please open an issue.

@@ -28,68 +28,68 @@ class #(-p) extends \PhpParser\ParserAbstract
public const %s = %n;
#endtokenval
protected $tokenToSymbolMapSize = #(YYMAXLEX);
protected $actionTableSize = #(YYLAST);
protected $gotoTableSize = #(YYGLAST);
protected int $tokenToSymbolMapSize = #(YYMAXLEX);
protected int $actionTableSize = #(YYLAST);
protected int $gotoTableSize = #(YYGLAST);
protected $invalidSymbol = #(YYBADCH);
protected $errorSymbol = #(YYINTERRTOK);
protected $defaultAction = #(YYDEFAULT);
protected $unexpectedTokenRule = #(YYUNEXPECTED);
protected int $invalidSymbol = #(YYBADCH);
protected int $errorSymbol = #(YYINTERRTOK);
protected int $defaultAction = #(YYDEFAULT);
protected int $unexpectedTokenRule = #(YYUNEXPECTED);
protected $YY2TBLSTATE = #(YY2TBLSTATE);
protected $numNonLeafStates = #(YYNLSTATES);
protected int $YY2TBLSTATE = #(YY2TBLSTATE);
protected int $numNonLeafStates = #(YYNLSTATES);
protected $symbolToName = array(
protected array $symbolToName = array(
#listvar terminals
);
protected $tokenToSymbol = array(
protected array $tokenToSymbol = array(
#listvar yytranslate
);
protected $action = array(
protected array $action = array(
#listvar yyaction
);
protected $actionCheck = array(
protected array $actionCheck = array(
#listvar yycheck
);
protected $actionBase = array(
protected array $actionBase = array(
#listvar yybase
);
protected $actionDefault = array(
protected array $actionDefault = array(
#listvar yydefault
);
protected $goto = array(
protected array $goto = array(
#listvar yygoto
);
protected $gotoCheck = array(
protected array $gotoCheck = array(
#listvar yygcheck
);
protected $gotoBase = array(
protected array $gotoBase = array(
#listvar yygbase
);
protected $gotoDefault = array(
protected array $gotoDefault = array(
#listvar yygdefault
);
protected $ruleToNonTerminal = array(
protected array $ruleToNonTerminal = array(
#listvar yylhs
);
protected $ruleToLength = array(
protected array $ruleToLength = array(
#listvar yylen
);
#if -t
protected $productions = array(
protected array $productions = array(
#production-strings;
);
#endif

@@ -366,21 +366,13 @@ inner_statement:
;
non_empty_statement:
'{' inner_statement_list '}'
{
if ($2) {
$$ = $2; prependLeadingComments($$);
} else {
makeNop($$);
if (null === $$) { $$ = array(); }
}
}
| T_IF '(' expr ')' statement elseif_list else_single
{ $$ = Stmt\If_[$3, ['stmts' => toArray($5), 'elseifs' => $6, 'else' => $7]]; }
'{' inner_statement_list '}' { $$ = Stmt\Block[$2]; }
| T_IF '(' expr ')' blocklike_statement elseif_list else_single
{ $$ = Stmt\If_[$3, ['stmts' => $5, 'elseifs' => $6, 'else' => $7]]; }
| T_IF '(' expr ')' ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';'
{ $$ = Stmt\If_[$3, ['stmts' => $6, 'elseifs' => $7, 'else' => $8]]; }
| T_WHILE '(' expr ')' while_statement { $$ = Stmt\While_[$3, $5]; }
| T_DO statement T_WHILE '(' expr ')' ';' { $$ = Stmt\Do_ [$5, toArray($2)]; }
| T_DO blocklike_statement T_WHILE '(' expr ')' ';' { $$ = Stmt\Do_ [$5, $2]; }
| T_FOR '(' for_expr ';' for_expr ';' for_expr ')' for_statement
{ $$ = Stmt\For_[['init' => $3, 'cond' => $5, 'loop' => $7, 'stmts' => $9]]; }
| T_SWITCH '(' expr ')' switch_case_list { $$ = Stmt\Switch_[$3, $5]; }
@@ -394,16 +386,7 @@ non_empty_statement:
$$ = Stmt\InlineHTML[$1];
$$->setAttribute('hasLeadingNewline', $this->inlineHtmlHasLeadingNewline(#1));
}
| expr semi {
$e = $1;
if ($e instanceof Expr\Throw_) {
// For backwards-compatibility reasons, convert throw in statement position into
// Stmt\Throw_ rather than Stmt\Expression(Expr\Throw_).
$$ = Stmt\Throw_[$e->expr];
} else {
$$ = Stmt\Expression[$e];
}
}
| expr semi { $$ = Stmt\Expression[$1]; }
| T_UNSET '(' variables_list ')' semi { $$ = Stmt\Unset_[$3]; }
| T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement
{ $$ = Stmt\Foreach_[$3, $5[0], ['keyVar' => null, 'byRef' => $5[1], 'stmts' => $7]]; }
@@ -416,14 +399,16 @@ non_empty_statement:
{ $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); }
| T_GOTO identifier_not_reserved semi { $$ = Stmt\Goto_[$2]; }
| identifier_not_reserved ':' { $$ = Stmt\Label[$1]; }
| error { $$ = array(); /* means: no statement */ }
| error { $$ = null; /* means: no statement */ }
;
statement:
non_empty_statement
| ';'
{ makeNop($$);
if ($$ === null) $$ = array(); /* means: no statement */ }
| ';' { makeNop($$); }
;
blocklike_statement:
statement { toBlock($1); }
;
catches:
@@ -554,17 +539,17 @@ non_empty_class_name_list:
;
for_statement:
statement { $$ = toArray($1); }
blocklike_statement
| ':' inner_statement_list T_ENDFOR ';' { $$ = $2; }
;
foreach_statement:
statement { $$ = toArray($1); }
blocklike_statement
| ':' inner_statement_list T_ENDFOREACH ';' { $$ = $2; }
;
declare_statement:
non_empty_statement { $$ = toArray($1); }
non_empty_statement { toBlock($1); }
| ';' { $$ = null; }
| ':' inner_statement_list T_ENDDECLARE ';' { $$ = $2; }
;
@@ -624,7 +609,7 @@ match_arm:
;
while_statement:
statement { $$ = toArray($1); }
blocklike_statement { $$ = $1; }
| ':' inner_statement_list T_ENDWHILE ';' { $$ = $2; }
;
@@ -634,7 +619,7 @@ elseif_list:
;
elseif:
T_ELSEIF '(' expr ')' statement { $$ = Stmt\ElseIf_[$3, toArray($5)]; }
T_ELSEIF '(' expr ')' blocklike_statement { $$ = Stmt\ElseIf_[$3, $5]; }
;
new_elseif_list:
@@ -649,7 +634,7 @@ new_elseif:
else_single:
/* empty */ { $$ = null; }
| T_ELSE statement { $$ = Stmt\Else_[toArray($2)]; }
| T_ELSE blocklike_statement { $$ = Stmt\Else_[$2]; }
;
new_else_single:
@@ -831,7 +816,7 @@ static_var:
;
class_statement_list_ex:
class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } }
class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } else { $$ = $1; } }
| /* empty */ { init(); }
;
@@ -1151,9 +1136,9 @@ exit_expr:
backticks_expr:
/* empty */ { $$ = array(); }
| T_ENCAPSED_AND_WHITESPACE
{ $$ = array(Node\InterpolatedStringPart[Scalar\String_::parseEscapeSequences($1, '`')]); }
| encaps_list { parseEncapsed($1, '`', true); $$ = $1; }
| encaps_string_part
{ $$ = array($1); parseEncapsed($$, '`', $this->phpVersion->supportsUnicodeEscapes()); }
| encaps_list { parseEncapsed($1, '`', $this->phpVersion->supportsUnicodeEscapes()); $$ = $1; }
;
ctor_arguments:
@@ -1196,10 +1181,11 @@ dereferencable_scalar:
$$ = new Expr\Array_($3, $attrs);
$this->createdArrays->attach($$); }
| array_short_syntax { $$ = $1; $this->createdArrays->attach($$); }
| T_CONSTANT_ENCAPSED_STRING { $$ = Scalar\String_::fromString($1, attributes()); }
| T_CONSTANT_ENCAPSED_STRING
{ $$ = Scalar\String_::fromString($1, attributes(), $this->phpVersion->supportsUnicodeEscapes()); }
| '"' encaps_list '"'
{ $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
parseEncapsed($2, '"', true); $$ = new Scalar\InterpolatedString($2, $attrs); }
parseEncapsed($2, '"', $this->phpVersion->supportsUnicodeEscapes()); $$ = new Scalar\InterpolatedString($2, $attrs); }
;
scalar:
@@ -1352,7 +1338,8 @@ encaps_list:
;
encaps_string_part:
T_ENCAPSED_AND_WHITESPACE { $$ = Node\InterpolatedStringPart[$1]; }
T_ENCAPSED_AND_WHITESPACE
{ $attrs = attributes(); $attrs['rawValue'] = $1; $$ = new Node\InterpolatedStringPart($1, $attrs); }
;
encaps_str_varname:

@@ -87,14 +87,15 @@ function resolveMacros($code) {
if ('pushNormalizing' === $name) {
assertArgs(2, $args, $name);
return 'if (is_array(' . $args[1] . ')) { $$ = array_merge(' . $args[0] . ', ' . $args[1] . '); }'
. ' else { ' . $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0] . '; }';
return 'if (' . $args[1] . ' !== null) { ' . $args[0] . '[] = ' . $args[1] . '; } $$ = ' . $args[0] . ';';
}
if ('toArray' == $name) {
if ('toBlock' == $name) {
assertArgs(1, $args, $name);
return 'is_array(' . $args[0] . ') ? ' . $args[0] . ' : array(' . $args[0] . ')';
return 'if (' . $args[0] . ' instanceof Stmt\Block) { $$ = ' . $args[0] . '->stmts; } '
. 'else if (' . $args[0] . ' === null) { $$ = []; } '
. 'else { $$ = [' . $args[0] . ']; }';
}
if ('parseVar' === $name) {
@@ -122,15 +123,6 @@ function resolveMacros($code) {
return $args[0] . ' = $this->maybeCreateZeroLengthNop($this->tokenPos);';
}
if ('prependLeadingComments' === $name) {
assertArgs(1, $args, $name);
return '$comments = $this->getCommentsBeforeToken($this->tokenStartStack[#1]); $stmts = ' . $args[0] . '; '
. 'if (!empty($comments)) {'
. '$stmts[0]->setAttribute(\'comments\', '
. 'array_merge($comments, $stmts[0]->getAttribute(\'comments\', []))); }';
}
return $matches[0];
},
$code

@@ -195,12 +195,6 @@ class TokenStream {
return false;
}
public function haveBracesInRange(int $startPos, int $endPos): bool {
return $this->haveTokenInRange($startPos, $endPos, '{')
|| $this->haveTokenInRange($startPos, $endPos, T_CURLY_OPEN)
|| $this->haveTokenInRange($startPos, $endPos, '}');
}
public function haveTagInRange(int $startPos, int $endPos): bool {
return $this->haveTokenInRange($startPos, $endPos, \T_OPEN_TAG)
|| $this->haveTokenInRange($startPos, $endPos, \T_CLOSE_TAG);

@@ -76,7 +76,7 @@ class Lexer {
$numTokens = \count($tokens);
if ($numTokens === 0) {
// Empty input edge case: Just add the sentinel token.
$tokens[] = [new Token(0, "\0", 1, 0)];
$tokens[] = new Token(0, "\0", 1, 0);
return;
}

@@ -7,8 +7,10 @@ use PhpParser\Node\Expr;
use PhpParser\Node\FunctionLike;
class ArrowFunction extends Expr implements FunctionLike {
/** @var bool Whether the closure is static */
public bool $static;
/** @var bool Whether to return by reference */
public bool $byRef;
/** @var Node\Param[] */
@@ -17,6 +19,7 @@ class ArrowFunction extends Expr implements FunctionLike {
/** @var null|Node\Identifier|Node\Name|Node\ComplexType */
public ?Node $returnType;
/** @var Expr Expression body */
public Expr $expr;
/** @var Node\AttributeGroup[] */
public array $attrGroups;

@@ -6,12 +6,15 @@ use PhpParser\Node;
use PhpParser\Node\MatchArm;
class Match_ extends Node\Expr {
/** @var Node\Expr Condition */
public Node\Expr $cond;
/** @var MatchArm[] */
public array $arms;
/**
* @param Node\Expr $cond Condition
* @param MatchArm[] $arms
* @param array<string, mixed> $attributes Additional attributes
*/
public function __construct(Node\Expr $cond, array $arms = [], array $attributes = []) {
$this->attributes = $attributes;

@@ -17,6 +17,7 @@ class Param extends NodeAbstract {
public Expr $var;
/** @var null|Expr Default value */
public ?Expr $default;
/** @var int Optional visibility flags */
public int $flags;
/** @var AttributeGroup[] PHP attribute groups */
public array $attrGroups;

@@ -0,0 +1,29 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node\Stmt;
class Block extends Stmt {
/** @var Stmt[] Statements */
public array $stmts;
/**
* A block of statements.
*
* @param Stmt[] $stmts Statements
* @param array<string, mixed> $attributes Additional attributes
*/
public function __construct(array $stmts, array $attributes = []) {
$this->attributes = $attributes;
$this->stmts = $stmts;
}
public function getType(): string {
return 'Stmt_Block';
}
public function getSubNodeNames(): array {
return ['stmts'];
}
}

@@ -1,29 +0,0 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class Throw_ extends Node\Stmt {
/** @var Node\Expr Expression */
public Node\Expr $expr;
/**
* Constructs a legacy throw statement node.
*
* @param Node\Expr $expr Expression
* @param array<string, mixed> $attributes Additional attributes
*/
public function __construct(Node\Expr $expr, array $attributes = []) {
$this->attributes = $attributes;
$this->expr = $expr;
}
public function getSubNodeNames(): array {
return ['expr'];
}
public function getType(): string {
return 'Stmt_Throw';
}
}

@@ -2,7 +2,12 @@
namespace PhpParser;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\Include_;
use PhpParser\Node\Expr\List_;
use PhpParser\Node\Scalar\Int_;
use PhpParser\Node\Scalar\InterpolatedString;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\GroupUse;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\UseItem;
@@ -10,7 +15,20 @@ use PhpParser\Node\UseItem;
class NodeDumper {
private bool $dumpComments;
private bool $dumpPositions;
private bool $dumpOtherAttributes;
private ?string $code;
private string $res;
private string $nl;
private const IGNORE_ATTRIBUTES = [
'comments' => true,
'startLine' => true,
'endLine' => true,
'startFilePos' => true,
'endFilePos' => true,
'startTokenPos' => true,
'endTokenPos' => true,
];
/**
* Constructs a NodeDumper.
@@ -19,12 +37,14 @@ class NodeDumper {
* * bool dumpComments: Whether comments should be dumped.
* * bool dumpPositions: Whether line/offset information should be dumped. To dump offset
* information, the code needs to be passed to dump().
* * bool dumpOtherAttributes: Whether non-comment, non-position attributes should be dumped.
*
* @param array $options Options (see description)
*/
public function __construct(array $options = []) {
$this->dumpComments = !empty($options['dumpComments']);
$this->dumpPositions = !empty($options['dumpPositions']);
$this->dumpOtherAttributes = !empty($options['dumpOtherAttributes']);
}
/**
@@ -39,72 +59,107 @@ class NodeDumper {
*/
public function dump($node, ?string $code = null): string {
$this->code = $code;
return $this->dumpRecursive($node);
$this->res = '';
$this->nl = "\n";
$this->dumpRecursive($node, false);
return $this->res;
}
/** @param Node|Comment|array $node */
protected function dumpRecursive($node): string {
/** @param mixed $node */
protected function dumpRecursive($node, bool $indent = true): void {
if ($indent) {
$this->nl .= " ";
}
if ($node instanceof Node) {
$r = $node->getType();
$this->res .= $node->getType();
if ($this->dumpPositions && null !== $p = $this->dumpPosition($node)) {
$r .= $p;
$this->res .= $p;
}
$r .= '(';
$this->res .= '(';
foreach ($node->getSubNodeNames() as $key) {
$r .= "\n " . $key . ': ';
$this->res .= "$this->nl " . $key . ': ';
$value = $node->$key;
if (null === $value) {
$r .= 'null';
} elseif (false === $value) {
$r .= 'false';
} elseif (true === $value) {
$r .= 'true';
} elseif (is_scalar($value)) {
if (\is_int($value)) {
if ('flags' === $key || 'newModifier' === $key) {
$r .= $this->dumpFlags($value);
} elseif ('type' === $key && $node instanceof Include_) {
$r .= $this->dumpIncludeType($value);
} elseif ('type' === $key
&& ($node instanceof Use_ || $node instanceof UseItem || $node instanceof GroupUse)) {
$r .= $this->dumpUseType($value);
} else {
$r .= $value;
$this->res .= $this->dumpFlags($value);
continue;
}
if ('type' === $key && $node instanceof Include_) {
$this->res .= $this->dumpIncludeType($value);
continue;
}
if ('type' === $key
&& ($node instanceof Use_ || $node instanceof UseItem || $node instanceof GroupUse)) {
$this->res .= $this->dumpUseType($value);
continue;
}
} else {
$r .= str_replace("\n", "\n ", $this->dumpRecursive($value));
}
$this->dumpRecursive($value);
}
if ($this->dumpComments && $comments = $node->getComments()) {
$r .= "\n comments: " . str_replace("\n", "\n ", $this->dumpRecursive($comments));
$this->res .= "$this->nl comments: ";
$this->dumpRecursive($comments);
}
} elseif (is_array($node)) {
$r = 'array(';
foreach ($node as $key => $value) {
$r .= "\n " . $key . ': ';
if ($this->dumpOtherAttributes) {
foreach ($node->getAttributes() as $key => $value) {
if (isset(self::IGNORE_ATTRIBUTES[$key])) {
continue;
}
if (null === $value) {
$r .= 'null';
} elseif (false === $value) {
$r .= 'false';
} elseif (true === $value) {
$r .= 'true';
} elseif (is_scalar($value)) {
$r .= $value;
} else {
$r .= str_replace("\n", "\n ", $this->dumpRecursive($value));
$this->res .= "$this->nl $key: ";
if (\is_int($value)) {
if ('kind' === $key) {
if ($node instanceof Int_) {
$this->res .= $this->dumpIntKind($value);
continue;
}
if ($node instanceof String_ || $node instanceof InterpolatedString) {
$this->res .= $this->dumpStringKind($value);
continue;
}
if ($node instanceof Array_) {
$this->res .= $this->dumpArrayKind($value);
continue;
}
if ($node instanceof List_) {
$this->res .= $this->dumpListKind($value);
continue;
}
}
}
$this->dumpRecursive($value);
}
}
$this->res .= "$this->nl)";
} elseif (\is_array($node)) {
$this->res .= 'array(';
foreach ($node as $key => $value) {
$this->res .= "$this->nl " . $key . ': ';
$this->dumpRecursive($value);
}
$this->res .= "$this->nl)";
} elseif ($node instanceof Comment) {
return $node->getReformattedText();
$this->res .= \str_replace("\n", $this->nl, $node->getReformattedText());
} elseif (\is_string($node)) {
$this->res .= \str_replace("\n", $this->nl, (string)$node);
} elseif (\is_int($node) || \is_float($node)) {
$this->res .= $node;
} elseif (null === $node) {
$this->res .= 'null';
} elseif (false === $node) {
$this->res .= 'false';
} elseif (true === $node) {
$this->res .= 'true';
} else {
throw new \InvalidArgumentException('Can only dump nodes and arrays.');
}
return $r . "\n)";
if ($indent) {
$this->nl = \substr($this->nl, 0, -4);
}
}
protected function dumpFlags(int $flags): string {
@@ -138,32 +193,62 @@ class NodeDumper {
}
}
protected function dumpIncludeType(int $type): string {
$map = [
/** @param array<int, string> $map */
private function dumpEnum(int $value, array $map): string {
if (!isset($map[$value])) {
return (string) $value;
}
return $map[$value] . ' (' . $value . ')';
}
private function dumpIncludeType(int $type): string {
return $this->dumpEnum($type, [
Include_::TYPE_INCLUDE => 'TYPE_INCLUDE',
Include_::TYPE_INCLUDE_ONCE => 'TYPE_INCLUDE_ONCE',
Include_::TYPE_REQUIRE => 'TYPE_REQUIRE',
Include_::TYPE_REQUIRE_ONCE => 'TYPE_REQUIRE_ONCE',
];
if (!isset($map[$type])) {
return (string) $type;
}
return $map[$type] . ' (' . $type . ')';
]);
}
protected function dumpUseType(int $type): string {
$map = [
private function dumpUseType(int $type): string {
return $this->dumpEnum($type, [
Use_::TYPE_UNKNOWN => 'TYPE_UNKNOWN',
Use_::TYPE_NORMAL => 'TYPE_NORMAL',
Use_::TYPE_FUNCTION => 'TYPE_FUNCTION',
Use_::TYPE_CONSTANT => 'TYPE_CONSTANT',
];
]);
}
if (!isset($map[$type])) {
return (string) $type;
}
return $map[$type] . ' (' . $type . ')';
private function dumpIntKind(int $kind): string {
return $this->dumpEnum($kind, [
Int_::KIND_BIN => 'KIND_BIN',
Int_::KIND_OCT => 'KIND_OCT',
Int_::KIND_DEC => 'KIND_DEC',
Int_::KIND_HEX => 'KIND_HEX',
]);
}
private function dumpStringKind(int $kind): string {
return $this->dumpEnum($kind, [
String_::KIND_SINGLE_QUOTED => 'KIND_SINGLE_QUOTED',
String_::KIND_DOUBLE_QUOTED => 'KIND_DOUBLE_QUOTED',
String_::KIND_HEREDOC => 'KIND_HEREDOC',
String_::KIND_NOWDOC => 'KIND_NOWDOC',
]);
}
private function dumpArrayKind(int $kind): string {
return $this->dumpEnum($kind, [
Array_::KIND_LONG => 'KIND_LONG',
Array_::KIND_SHORT => 'KIND_SHORT',
]);
}
private function dumpListKind(int $kind): string {
return $this->dumpEnum($kind, [
List_::KIND_LIST => 'KIND_LIST',
List_::KIND_ARRAY => 'KIND_ARRAY',
]);
}
/**

@@ -15,6 +15,10 @@ class NodeFinder {
* @return Node[] Found nodes satisfying the filter callback
*/
public function find($nodes, callable $filter): array {
if ($nodes === []) {
return [];
}
if (!is_array($nodes)) {
$nodes = [$nodes];
}
@@ -52,6 +56,10 @@ class NodeFinder {
* @return null|Node Found node (or null if none found)
*/
public function findFirst($nodes, callable $filter): ?Node {
if ($nodes === []) {
return null;
}
if (!is_array($nodes)) {
$nodes = [$nodes];
}

@@ -34,7 +34,7 @@ class NodeTraverser implements NodeTraverserInterface {
*
* @param NodeVisitor ...$visitors Node visitors
*/
public function __construct(NodeVisitor... $visitors) {
public function __construct(NodeVisitor ...$visitors) {
$this->visitors = $visitors;
}
@@ -175,7 +175,7 @@ class NodeTraverser implements NodeTraverserInterface {
protected function traverseArray(array $nodes): array {
$doNodes = [];
foreach ($nodes as $i => &$node) {
foreach ($nodes as $i => $node) {
if ($node instanceof Node) {
$traverseChildren = true;
$visitorIndex = -1;
@@ -185,7 +185,7 @@ class NodeTraverser implements NodeTraverserInterface {
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($node, $return);
$node = $return;
$nodes[$i] = $node = $return;
} elseif (\is_array($return)) {
$doNodes[] = [$i, $return];
continue 2;
@@ -225,7 +225,7 @@ class NodeTraverser implements NodeTraverserInterface {
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($node, $return);
$node = $return;
$nodes[$i] = $node = $return;
} elseif (\is_array($return)) {
$doNodes[] = [$i, $return];
break;

@@ -0,0 +1,82 @@
<?php declare(strict_types=1);
namespace PhpParser\NodeVisitor;
use PhpParser\Comment;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
use PhpParser\Token;
class CommentAnnotatingVisitor extends NodeVisitorAbstract {
/** @var int Last seen token start position */
private int $pos = 0;
/** @var Token[] Token array */
private array $tokens;
/** @var list<int> Token positions of comments */
private array $commentPositions = [];
/**
* Create a comment annotation visitor.
*
* @param Token[] $tokens Token array
*/
public function __construct(array $tokens) {
$this->tokens = $tokens;
// Collect positions of comments. We use this to avoid traversing parts of the AST where
// there are no comments.
foreach ($tokens as $i => $token) {
if ($token->id === \T_COMMENT || $token->id === \T_DOC_COMMENT) {
$this->commentPositions[] = $i;
}
}
}
public function enterNode(Node $node) {
$nextCommentPos = current($this->commentPositions);
if ($nextCommentPos === false) {
// No more comments.
return self::STOP_TRAVERSAL;
}
$oldPos = $this->pos;
$this->pos = $pos = $node->getStartTokenPos();
if ($nextCommentPos > $oldPos && $nextCommentPos < $pos) {
$comments = [];
while (--$pos >= $oldPos) {
$token = $this->tokens[$pos];
if ($token->id === \T_DOC_COMMENT) {
$comments[] = new Comment\Doc(
$token->text, $token->line, $token->pos, $pos,
$token->getEndLine(), $token->getEndPos() - 1, $pos);
continue;
}
if ($token->id === \T_COMMENT) {
$comments[] = new Comment(
$token->text, $token->line, $token->pos, $pos,
$token->getEndLine(), $token->getEndPos() - 1, $pos);
continue;
}
if ($token->id !== \T_WHITESPACE) {
break;
}
}
if (!empty($comments)) {
$node->setAttribute('comments', array_reverse($comments));
}
do {
$nextCommentPos = next($this->commentPositions);
} while ($nextCommentPos !== false && $nextCommentPos < $this->pos);
}
$endPos = $node->getEndTokenPos();
if ($nextCommentPos > $endPos) {
// Skip children if there are no comments located inside this node.
$this->pos = $endPos;
return self::DONT_TRAVERSE_CHILDREN;
}
return null;
}
}

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@@ -30,6 +30,7 @@ use PhpParser\Node\Stmt\Nop;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\TryCatch;
use PhpParser\Node\UseItem;
use PhpParser\NodeVisitor\CommentAnnotatingVisitor;
abstract class ParserAbstract implements Parser {
private const SYMBOL_NONE = -1;
@@ -201,6 +202,11 @@ abstract class ParserAbstract implements Parser {
$this->semValue = null;
$this->createdArrays = null;
if ($result !== null) {
$traverser = new NodeTraverser(new CommentAnnotatingVisitor($this->tokens));
$traverser->traverse($result);
}
return $result;
}
@@ -473,7 +479,7 @@ abstract class ParserAbstract implements Parser {
protected function getAttributes(int $tokenStartPos, int $tokenEndPos): array {
$startToken = $this->tokens[$tokenStartPos];
$afterEndToken = $this->tokens[$tokenEndPos + 1];
$attributes = [
return [
'startLine' => $startToken->line,
'startTokenPos' => $tokenStartPos,
'startFilePos' => $startToken->pos,
@@ -481,11 +487,6 @@ abstract class ParserAbstract implements Parser {
'endTokenPos' => $tokenEndPos,
'endFilePos' => $afterEndToken->pos - 1,
];
$comments = $this->getCommentsBeforeToken($tokenStartPos);
if (!empty($comments)) {
$attributes['comments'] = $comments;
}
return $attributes;
}
/**
@@ -500,7 +501,7 @@ abstract class ParserAbstract implements Parser {
// Get attributes for the sentinel token.
$token = $this->tokens[$tokenPos];
$attributes = [
return [
'startLine' => $token->line,
'startTokenPos' => $tokenPos,
'startFilePos' => $token->pos,
@@ -508,11 +509,6 @@ abstract class ParserAbstract implements Parser {
'endTokenPos' => $tokenPos,
'endFilePos' => $token->pos,
];
$comments = $this->getCommentsBeforeToken($tokenPos);
if (!empty($comments)) {
$attributes['comments'] = $comments;
}
return $attributes;
}
/*
@@ -585,7 +581,7 @@ abstract class ParserAbstract implements Parser {
} else {
// For semicolon namespaces we have to move the statements after a namespace declaration into ->stmts
$resultStmts = [];
$targetStmts =& $resultStmts;
$targetStmts = &$resultStmts;
$lastNs = null;
foreach ($stmts as $stmt) {
if ($stmt instanceof Node\Stmt\Namespace_) {
@@ -594,12 +590,12 @@ abstract class ParserAbstract implements Parser {
}
if ($stmt->stmts === null) {
$stmt->stmts = [];
$targetStmts =& $stmt->stmts;
$targetStmts = &$stmt->stmts;
$resultStmts[] = $stmt;
} else {
// This handles the invalid case of mixed style namespaces
$resultStmts[] = $stmt;
$targetStmts =& $resultStmts;
$targetStmts = &$resultStmts;
}
$lastNs = $stmt;
} elseif ($stmt instanceof Node\Stmt\HaltCompiler) {
@@ -845,6 +841,7 @@ abstract class ParserAbstract implements Parser {
if (\is_string($contents)) {
if ($contents === '') {
$attributes['rawValue'] = $contents;
return new String_('', $attributes);
}
@@ -852,6 +849,7 @@ abstract class ParserAbstract implements Parser {
$contents, $indentLen, $indentChar, true, true, $attributes
);
$contents = preg_replace('~(\r\n|\n|\r)\z~', '', $contents);
$attributes['rawValue'] = $contents;
if ($kind === String_::KIND_HEREDOC) {
$contents = String_::parseEscapeSequences($contents, null, $parseUnicodeEscape);
@@ -878,6 +876,7 @@ abstract class ParserAbstract implements Parser {
if ($isLast) {
$part->value = preg_replace('~(\r\n|\n|\r)\z~', '', $part->value);
}
$part->setAttribute('rawValue', $part->value);
$part->value = String_::parseEscapeSequences($part->value, null, $parseUnicodeEscape);
if ('' === $part->value) {
continue;
@@ -899,12 +898,9 @@ abstract class ParserAbstract implements Parser {
}
/**
* Get comments before the given token position.
*
* @return Comment[] Comments
* Get last comment before the given token position, if any
*/
protected function getCommentsBeforeToken(int $tokenPos): array {
$comments = [];
protected function getCommentBeforeToken(int $tokenPos): ?Comment {
while (--$tokenPos >= 0) {
$token = $this->tokens[$tokenPos];
if (!isset($this->dropTokens[$token->id])) {
@@ -912,22 +908,21 @@ abstract class ParserAbstract implements Parser {
}
if ($token->id === \T_COMMENT || $token->id === \T_DOC_COMMENT) {
$comments[] = $this->createCommentFromToken($token, $tokenPos);
return $this->createCommentFromToken($token, $tokenPos);
}
}
return \array_reverse($comments);
return null;
}
/**
* Create a zero-length nop to capture preceding comments, if any.
*/
protected function maybeCreateZeroLengthNop(int $tokenPos): ?Nop {
$comments = $this->getCommentsBeforeToken($tokenPos);
if (empty($comments)) {
$comment = $this->getCommentBeforeToken($tokenPos);
if ($comment === null) {
return null;
}
$comment = $comments[\count($comments) - 1];
$commentEndLine = $comment->getEndLine();
$commentEndFilePos = $comment->getEndFilePos();
$commentEndTokenPos = $comment->getEndTokenPos();
@@ -938,14 +933,12 @@ abstract class ParserAbstract implements Parser {
'endFilePos' => $commentEndFilePos,
'startTokenPos' => $commentEndTokenPos + 1,
'endTokenPos' => $commentEndTokenPos,
'comments' => $comments,
];
return new Nop($attributes);
}
protected function maybeCreateNop(int $tokenStartPos, int $tokenEndPos): ?Nop {
$comments = $this->getCommentsBeforeToken($tokenStartPos);
if (empty($comments)) {
if ($this->getCommentBeforeToken($tokenStartPos) === null) {
return null;
}
return new Nop($this->getAttributes($tokenStartPos, $tokenEndPos));

@@ -6,34 +6,6 @@ use PhpParser\Parser\Php7;
use PhpParser\Parser\Php8;
class ParserFactory {
public const PREFER_PHP7 = 1;
public const ONLY_PHP7 = 3;
/**
* Creates a Parser instance, according to the provided kind.
*
* @param int $kind One of ::PREFER_PHP7 or ::ONLY_PHP7
* @param Lexer|null $lexer Lexer to use. Defaults to emulative lexer when not specified
*
* @return Parser The parser instance
*
* @deprecated Use createForVersion(), createForNewestSupportedVersion() or createForHostVersion() instead.
*/
public function create(int $kind, ?Lexer $lexer = null): Parser {
if (null === $lexer) {
$lexer = new Lexer\Emulative();
}
switch ($kind) {
case self::PREFER_PHP7:
case self::ONLY_PHP7:
return new Parser\Php7($lexer);
default:
throw new \LogicException(
'Kind must be one of ::PREFER_PHP7 or ::ONLY_PHP7'
);
}
}
/**
* Create a parser targeting the given version on a best-effort basis. The parser will generally
* accept code for the newest supported version, but will try to accommodate code that becomes

@@ -149,9 +149,16 @@ class PhpVersion {
}
/**
* Whether this version support yield in expression context without parentheses.
* Whether this version supports yield in expression context without parentheses.
*/
public function supportsYieldWithoutParentheses(): bool {
return $this->id >= 70000;
}
/**
* Whether this version supports unicode escape sequences in strings.
*/
public function supportsUnicodeEscapes(): bool {
return $this->id >= 70000;
}
}

@@ -960,10 +960,6 @@ class Standard extends PrettyPrinterAbstract {
return 'return' . (null !== $node->expr ? ' ' . $this->p($node->expr) : '') . ';';
}
protected function pStmt_Throw(Stmt\Throw_ $node): string {
return 'throw ' . $this->p($node->expr) . ';';
}
protected function pStmt_Label(Stmt\Label $node): string {
return $node->name . ':';
}
@@ -1012,6 +1008,10 @@ class Standard extends PrettyPrinterAbstract {
return '';
}
protected function pStmt_Block(Stmt\Block $node): string {
return '{' . $this->pStmts($node->stmts) . $this->nl . '}';
}
// Helpers
protected function pClassCommon(Stmt\Class_ $node, string $afterClassToken): string {

@@ -826,9 +826,8 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
}
if ($skipRemovedNode) {
if ($isStmtList && ($this->origTokens->haveBracesInRange($pos, $itemStartPos) ||
$this->origTokens->haveTagInRange($pos, $itemStartPos))) {
// We'd remove the brace of a code block.
if ($isStmtList && $this->origTokens->haveTagInRange($pos, $itemStartPos)) {
// We'd remove an opening/closing PHP tag.
// TODO: Preserve formatting.
$this->setIndentLevel($origIndentLevel);
return null;
@@ -937,9 +936,8 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
$pos, $itemStartPos, $indentAdjustment);
$skipRemovedNode = true;
} else {
if ($isStmtList && ($this->origTokens->haveBracesInRange($pos, $itemStartPos) ||
$this->origTokens->haveTagInRange($pos, $itemStartPos))) {
// We'd remove the brace of a code block.
if ($isStmtList && $this->origTokens->haveTagInRange($pos, $itemStartPos)) {
// We'd remove an opening/closing PHP tag.
// TODO: Preserve formatting.
return null;
}
@@ -1540,6 +1538,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
Stmt\Function_::class . '->stmts' => "\n",
Stmt\If_::class . '->stmts' => "\n",
Stmt\Namespace_::class . '->stmts' => "\n",
Stmt\Block::class . '->stmts' => "\n",
// Attribute groups
Stmt\Class_::class . '->attrGroups' => "\n",

@@ -100,11 +100,6 @@ parameters:
count: 1
path: lib/PhpParser/NodeDumper.php
-
message: "#^Method PhpParser\\\\NodeDumper\\:\\:dumpRecursive\\(\\) has parameter \\$node with no value type specified in iterable type array\\.$#"
count: 1
path: lib/PhpParser/NodeDumper.php
-
message: "#^Method PhpParser\\\\NodeTraverser\\:\\:traverseArray\\(\\) has parameter \\$nodes with no value type specified in iterable type array\\.$#"
count: 1

@@ -28,6 +28,7 @@ class CodeParsingTest extends CodeTestAbstract {
// Must be public for updateTests.php
public function getParseOutput(Parser $parser, $code, array $modes) {
$dumpPositions = isset($modes['positions']);
$dumpOtherAttributes = isset($modes['attributes']);
$errors = new ErrorHandler\Collecting();
$stmts = $parser->parse($code, $errors);
@@ -38,7 +39,11 @@ class CodeParsingTest extends CodeTestAbstract {
}
if (null !== $stmts) {
$dumper = new NodeDumper(['dumpComments' => true, 'dumpPositions' => $dumpPositions]);
$dumper = new NodeDumper([
'dumpComments' => true,
'dumpPositions' => $dumpPositions,
'dumpOtherAttributes' => $dumpOtherAttributes,
]);
$output .= $dumper->dump($stmts, $code);
}

@@ -8,7 +8,7 @@ use PhpParser\ParserFactory;
class DNumberTest extends \PHPUnit\Framework\TestCase {
public function testRawValue() {
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$parser = (new ParserFactory())->createForNewestSupportedVersion();
$nodes = $parser->parse('<?php echo 1_234.56;');
$echo = $nodes[0];

@@ -7,7 +7,7 @@ use PhpParser\ParserFactory;
class NumberTest extends \PHPUnit\Framework\TestCase {
public function testRawValue() {
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$parser = (new ParserFactory())->createForNewestSupportedVersion();
$nodes = $parser->parse('<?php echo 1_234;');
$echo = $nodes[0];

@@ -7,7 +7,7 @@ use PhpParser\ParserFactory;
class StringTest extends \PHPUnit\Framework\TestCase {
public function testRawValue() {
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$parser = (new ParserFactory())->createForNewestSupportedVersion();
$nodes = $parser->parse('<?php echo "sequence \x41";');
$echo = $nodes[0];

@@ -125,7 +125,7 @@ class NodeAbstractTest extends \PHPUnit\Framework\TestCase {
$this->assertSame('newValue', $node->subNode1);
// indirect modification
$subNode =& $node->subNode1;
$subNode = &$node->subNode1;
$subNode = 'newNewValue';
$this->assertSame('newNewValue', $node->subNode1);

@@ -188,7 +188,7 @@ class A extends B implements C, D {
E::h as i;
E::j insteadof F, G;
}
#[X]
public float $php = 7.4;
public ?Foo $person;
@@ -197,6 +197,10 @@ class A extends B implements C, D {
#[X]
const C = 1;
public const X A = X::Bar;
public const X\Foo B = X\Foo::Bar;
public const \X\Foo C = \X\Foo::Bar;
}
#[X]
@@ -262,6 +266,9 @@ class A extends \NS\B implements \NS\C, \NS\D
public \NS\A|\NS\B|int $prop;
#[\NS\X]
const C = 1;
public const \NS\X A = \NS\X::Bar;
public const \NS\X\Foo B = \NS\X\Foo::Bar;
public const \X\Foo C = \X\Foo::Bar;
}
#[\NS\X]
interface A extends \NS\C, \NS\D

@@ -11,7 +11,7 @@ use PhpParser\ParserFactory;
final class NodeConnectingVisitorTest extends \PHPUnit\Framework\TestCase {
public function testConnectsNodeToItsParentNodeAndItsSiblingNodes() {
$ast = (new ParserFactory())->create(ParserFactory::PREFER_PHP7)->parse(
$ast = (new ParserFactory())->createForNewestSupportedVersion()->parse(
'<?php if (true) {} else {}'
);

@@ -9,7 +9,7 @@ use PhpParser\ParserFactory;
final class ParentConnectingVisitorTest extends \PHPUnit\Framework\TestCase {
public function testConnectsChildNodeToParentNode() {
$ast = (new ParserFactory())->create(ParserFactory::PREFER_PHP7)->parse(
$ast = (new ParserFactory())->createForNewestSupportedVersion()->parse(
'<?php class C { public function m() {} }'
);

@@ -5,23 +5,13 @@ namespace PhpParser;
/* This test is very weak, because PHPUnit's assertEquals assertion is way too slow dealing with the
* large objects involved here. So we just do some basic instanceof tests instead. */
class ParserFactoryTest extends \PHPUnit\Framework\TestCase {
/** @dataProvider provideTestCreate */
public function testCreate($kind, $lexer, $expected) {
$this->assertInstanceOf($expected, (new ParserFactory())->create($kind, $lexer));
}
use PhpParser\Parser\Php7;
use PhpParser\Parser\Php8;
public function provideTestCreate() {
$lexer = new Lexer();
return [
[
ParserFactory::PREFER_PHP7, $lexer,
Parser\Php7::class
],
[
ParserFactory::ONLY_PHP7, null,
Parser\Php7::class
],
];
class ParserFactoryTest extends \PHPUnit\Framework\TestCase {
public function testCreate() {
$factory = new ParserFactory();
$this->assertInstanceOf(Php8::class, $factory->createForNewestSupportedVersion());
$this->assertInstanceOf(Parser::class, $factory->createForHostVersion());
}
}

@@ -28,7 +28,6 @@ class PrettyPrinterTest extends CodeTestAbstract {
/**
* @dataProvider provideTestPrettyPrint
* @covers \PhpParser\PrettyPrinter\Standard<extended>
*/
public function testPrettyPrint($name, $code, $expected, $mode) {
$this->doTestPrettyPrintMethod('prettyPrint', $name, $code, $expected, $mode);
@@ -36,7 +35,6 @@ class PrettyPrinterTest extends CodeTestAbstract {
/**
* @dataProvider provideTestPrettyPrintFile
* @covers \PhpParser\PrettyPrinter\Standard<extended>
*/
public function testPrettyPrintFile($name, $code, $expected, $mode) {
$this->doTestPrettyPrintMethod('prettyPrintFile', $name, $code, $expected, $mode);
@@ -181,7 +179,6 @@ class PrettyPrinterTest extends CodeTestAbstract {
/**
* @dataProvider provideTestFormatPreservingPrint
* @covers \PhpParser\PrettyPrinter\Standard<extended>
*/
public function testFormatPreservingPrint($name, $code, $modification, $expected, $modeLine) {
$lexer = new Lexer\Emulative();
@@ -218,7 +215,6 @@ CODE
/**
* @dataProvider provideTestRoundTripPrint
* @covers \PhpParser\PrettyPrinter\Standard<extended>
*/
public function testRoundTripPrint($name, $code, $expected, $modeLine) {
/**

@@ -153,8 +153,9 @@ $stmts[1] = $tmp;
* fallback. */
-----
<?php
$x;
{
$x;
}
function foo() {
foo();
/*
@@ -186,4 +187,4 @@ echo "{$bar}bar";
[$a
,$b
,
,] = $b;
,] = $b;

@@ -354,3 +354,16 @@ array_splice($stmts[0]->expr->var->items, 1, 0, [null]);
-----
<?php
list($x, , $y) = $z;
-----
<?php
{
$a; $b;
}
-----
$stmts[0]->stmts[] = new Stmt\Expression(new Expr\Variable('c'));
-----
<?php
{
$a; $b;
$c;
}

@@ -70,7 +70,6 @@ $y;
array_splice($stmts, 0, 1, []);
-----
<?php
$y;
-----
<?php
@@ -80,8 +79,7 @@ $x;
array_splice($stmts, 0, 1, []);
-----
<?php
$y;
{ $y; }
-----
<?php
$x;
@@ -90,7 +88,6 @@ $x;
array_pop($stmts);
-----
<?php
$x;
-----
<?php
@@ -100,8 +97,7 @@ $y;
array_pop($stmts);
-----
<?php
$x;
{ $x; }
-----
<?php
// Foo

@@ -15,20 +15,31 @@ Comments on blocks
{}
-----
array(
0: Stmt_Expression(
expr: Expr_Variable(
name: a
comments: array(
0: // baz
0: Stmt_Block(
stmts: array(
0: Stmt_Block(
stmts: array(
0: Stmt_Expression(
expr: Expr_Variable(
name: a
)
comments: array(
0: // baz
)
)
)
comments: array(
0: // bar
)
)
)
comments: array(
0: // foo
1: // bar
2: // baz
)
)
1: Stmt_Nop(
1: Stmt_Block(
stmts: array(
)
comments: array(
0: // empty
)

@@ -24,12 +24,6 @@ array(
0: Stmt_Expression(
expr: Expr_Variable(
name: var
comments: array(
0: /** doc 1 */
1: /* foobar 1 */
2: // foo 1
3: // bar 1
)
)
comments: array(
0: /** doc 1 */

@@ -0,0 +1,6 @@
Empty file
-----
-----
array(
)

@@ -223,13 +223,17 @@ array(
)
)
)
1: Stmt_Expression(
expr: Expr_Assign(
var: Expr_Variable(
name: j
)
expr: Scalar_Int(
value: 1
1: Stmt_Block(
stmts: array(
0: Stmt_Expression(
expr: Expr_Assign(
var: Expr_Variable(
name: j
)
expr: Scalar_Int(
value: 1
)
)
)
)
)
@@ -521,9 +525,11 @@ array(
)
)
)
12: Stmt_Throw(
expr: Expr_Variable(
name: x
12: Stmt_Expression(
expr: Expr_Throw(
expr: Expr_Variable(
name: x
)
)
)
13: Stmt_Goto(
@@ -832,11 +838,12 @@ array(
<?php
class Foo {
public $bar1;
publi $foo;
public $bar;
}
-----
Syntax error, unexpected T_STRING from 4:5 to 4:9
Syntax error, unexpected T_STRING from 5:5 to 5:9
array(
0: Stmt_Class(
attrGroups: array(
@@ -850,6 +857,20 @@ array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: PUBLIC (1)
type: null
props: array(
0: PropertyItem(
name: VarLikeIdentifier(
name: bar1
)
default: null
)
)
)
1: Stmt_Property(
attrGroups: array(
)
flags: PUBLIC (1)

@@ -116,9 +116,6 @@ array(
expr: Expr_Array(
items: array(
)
comments: array(
0: // short array syntax
)
)
comments: array(
0: // short array syntax

@@ -41,16 +41,10 @@ array(
expr: Expr_Assign(
var: Expr_Variable(
name: a
comments: array(
0: // simple assign
)
)
expr: Expr_Variable(
name: b
)
comments: array(
0: // simple assign
)
)
comments: array(
0: // simple assign
@@ -60,16 +54,10 @@ array(
expr: Expr_AssignOp_BitwiseAnd(
var: Expr_Variable(
name: a
comments: array(
0: // combined assign
)
)
expr: Expr_Variable(
name: b
)
comments: array(
0: // combined assign
)
)
comments: array(
0: // combined assign
@@ -199,9 +187,6 @@ array(
expr: Expr_Assign(
var: Expr_Variable(
name: a
comments: array(
0: // chained assign
)
)
expr: Expr_AssignOp_Mul(
var: Expr_Variable(
@@ -216,9 +201,6 @@ array(
)
)
)
comments: array(
0: // chained assign
)
)
comments: array(
0: // chained assign
@@ -228,16 +210,10 @@ array(
expr: Expr_AssignRef(
var: Expr_Variable(
name: a
comments: array(
0: // by ref assign
)
)
expr: Expr_Variable(
name: b
)
comments: array(
0: // by ref assign
)
)
comments: array(
0: // by ref assign
@@ -256,16 +232,10 @@ array(
unpack: false
)
)
comments: array(
0: // list() assign
)
)
expr: Expr_Variable(
name: b
)
comments: array(
0: // list() assign
)
)
comments: array(
0: // list() assign
@@ -349,9 +319,6 @@ array(
var: Expr_Variable(
name: a
)
comments: array(
0: // inc/dec
)
)
comments: array(
0: // inc/dec

@@ -17,9 +17,6 @@ array(
name: b
)
)
comments: array(
0: // This is legal.
)
)
comments: array(
0: // This is legal.
@@ -37,9 +34,6 @@ array(
)
)
)
comments: array(
0: // This is illegal, but not a syntax error.
)
)
comments: array(
0: // This is illegal, but not a syntax error.

@@ -29,16 +29,10 @@ array(
unpack: false
)
)
comments: array(
0: // This is legal.
)
)
expr: Expr_Variable(
name: x
)
comments: array(
0: // This is legal.
)
)
comments: array(
0: // This is legal.
@@ -62,16 +56,10 @@ array(
unpack: false
)
)
comments: array(
0: // This is illegal, but not a syntax error.
)
)
expr: Expr_Variable(
name: x
)
comments: array(
0: // This is illegal, but not a syntax error.
)
)
comments: array(
0: // This is illegal, but not a syntax error.

@@ -20,15 +20,9 @@ array(
expr: Expr_FuncCall(
name: Name(
name: a
comments: array(
0: // function name variations
)
)
args: array(
)
comments: array(
0: // function name variations
)
)
comments: array(
0: // function name variations
@@ -130,22 +124,13 @@ array(
var: Expr_FuncCall(
name: Name(
name: a
comments: array(
0: // array dereferencing
)
)
args: array(
)
comments: array(
0: // array dereferencing
)
)
dim: Scalar_String(
value: b
)
comments: array(
0: // array dereferencing
)
)
comments: array(
0: // array dereferencing

@@ -22,16 +22,10 @@ array(
expr: Expr_PropertyFetch(
var: Expr_Variable(
name: a
comments: array(
0: // property fetch variations
)
)
name: Identifier(
name: b
)
comments: array(
0: // property fetch variations
)
)
comments: array(
0: // property fetch variations
@@ -71,18 +65,12 @@ array(
expr: Expr_MethodCall(
var: Expr_Variable(
name: a
comments: array(
0: // method call variations
)
)
name: Identifier(
name: b
)
args: array(
)
comments: array(
0: // method call variations
)
)
comments: array(
0: // method call variations
@@ -136,25 +124,16 @@ array(
var: Expr_MethodCall(
var: Expr_Variable(
name: a
comments: array(
0: // array dereferencing
)
)
name: Identifier(
name: b
)
args: array(
)
comments: array(
0: // array dereferencing
)
)
dim: Scalar_String(
value: c
)
comments: array(
0: // array dereferencing
)
)
comments: array(
0: // array dereferencing

@@ -23,18 +23,12 @@ array(
expr: Expr_StaticCall(
class: Name(
name: A
comments: array(
0: // method name variations
)
)
name: Identifier(
name: b
)
args: array(
)
comments: array(
0: // method name variations
)
)
comments: array(
0: // method name variations
@@ -112,25 +106,16 @@ array(
var: Expr_StaticCall(
class: Name(
name: A
comments: array(
0: // array dereferencing
)
)
name: Identifier(
name: b
)
args: array(
)
comments: array(
0: // array dereferencing
)
)
dim: Scalar_String(
value: c
)
comments: array(
0: // array dereferencing
)
)
comments: array(
0: // array dereferencing
@@ -140,18 +125,12 @@ array(
expr: Expr_StaticCall(
class: Name(
name: static
comments: array(
0: // class name variations
)
)
name: Identifier(
name: b
)
args: array(
)
comments: array(
0: // class name variations
)
)
comments: array(
0: // class name variations

@@ -18,16 +18,10 @@ array(
expr: Expr_StaticPropertyFetch(
class: Name(
name: A
comments: array(
0: // property name variations
)
)
name: VarLikeIdentifier(
name: b
)
comments: array(
0: // property name variations
)
)
comments: array(
0: // property name variations
@@ -58,23 +52,14 @@ array(
var: Expr_StaticPropertyFetch(
class: Name(
name: A
comments: array(
0: // array access
)
)
name: VarLikeIdentifier(
name: b
)
comments: array(
0: // array access
)
)
dim: Scalar_String(
value: c
)
comments: array(
0: // array access
)
)
comments: array(
0: // array access

@@ -61,9 +61,6 @@ array(
0: VariadicPlaceholder(
)
)
comments: array(
0: // These are invalid, but accepted on the parser level.
)
)
comments: array(
0: // These are invalid, but accepted on the parser level.

@@ -25,16 +25,10 @@ array(
expr: Expr_BinaryOp_BooleanAnd(
left: Expr_Variable(
name: a
comments: array(
0: // boolean ops
)
)
right: Expr_Variable(
name: b
)
comments: array(
0: // boolean ops
)
)
comments: array(
0: // boolean ops
@@ -70,16 +64,10 @@ array(
expr: Expr_BinaryOp_LogicalAnd(
left: Expr_Variable(
name: a
comments: array(
0: // logical ops
)
)
right: Expr_Variable(
name: b
)
comments: array(
0: // logical ops
)
)
comments: array(
0: // logical ops
@@ -110,16 +98,10 @@ array(
left: Expr_BinaryOp_BooleanAnd(
left: Expr_Variable(
name: a
comments: array(
0: // precedence
)
)
right: Expr_Variable(
name: b
)
comments: array(
0: // precedence
)
)
right: Expr_BinaryOp_BooleanAnd(
left: Expr_Variable(
@@ -129,9 +111,6 @@ array(
name: d
)
)
comments: array(
0: // precedence
)
)
comments: array(
0: // precedence

@@ -63,9 +63,6 @@ array(
conds: array(
0: Scalar_Int(
value: 0
comments: array(
0: // list of conditions
)
)
1: Scalar_Int(
value: 1

@@ -39,9 +39,6 @@ array(
expr: Expr_Variable(
name: a
)
comments: array(
0: // unary ops
)
)
comments: array(
0: // unary ops
@@ -65,16 +62,10 @@ array(
expr: Expr_BinaryOp_BitwiseAnd(
left: Expr_Variable(
name: a
comments: array(
0: // binary ops
)
)
right: Expr_Variable(
name: b
)
comments: array(
0: // binary ops
)
)
comments: array(
0: // binary ops
@@ -195,23 +186,14 @@ array(
left: Expr_BinaryOp_Mul(
left: Expr_Variable(
name: a
comments: array(
0: // associativity
)
)
right: Expr_Variable(
name: b
)
comments: array(
0: // associativity
)
)
right: Expr_Variable(
name: c
)
comments: array(
0: // associativity
)
)
comments: array(
0: // associativity
@@ -236,9 +218,6 @@ array(
expr: Expr_BinaryOp_Plus(
left: Expr_Variable(
name: a
comments: array(
0: // precedence
)
)
right: Expr_BinaryOp_Mul(
left: Expr_Variable(
@@ -248,9 +227,6 @@ array(
name: c
)
)
comments: array(
0: // precedence
)
)
comments: array(
0: // precedence
@@ -275,9 +251,6 @@ array(
expr: Expr_BinaryOp_Pow(
left: Expr_Variable(
name: a
comments: array(
0: // pow is special
)
)
right: Expr_BinaryOp_Pow(
left: Expr_Variable(
@@ -287,9 +260,6 @@ array(
name: c
)
)
comments: array(
0: // pow is special
)
)
comments: array(
0: // pow is special

@@ -52,9 +52,6 @@ array(
)
args: array(
)
comments: array(
0: // class name variations
)
)
comments: array(
0: // class name variations
@@ -100,9 +97,6 @@ array(
)
args: array(
)
comments: array(
0: // DNCR object access
)
)
comments: array(
0: // DNCR object access

@@ -21,9 +21,6 @@ array(
expr: Expr_Ternary(
cond: Expr_Variable(
name: a
comments: array(
0: // ternary
)
)
if: Expr_Variable(
name: b
@@ -31,9 +28,6 @@ array(
else: Expr_Variable(
name: c
)
comments: array(
0: // ternary
)
)
comments: array(
0: // ternary
@@ -55,9 +49,6 @@ array(
cond: Expr_Ternary(
cond: Expr_Variable(
name: a
comments: array(
0: // precedence
)
)
if: Expr_Variable(
name: b
@@ -65,9 +56,6 @@ array(
else: Expr_Variable(
name: c
)
comments: array(
0: // precedence
)
)
if: Expr_Variable(
name: d
@@ -75,9 +63,6 @@ array(
else: Expr_Variable(
name: e
)
comments: array(
0: // precedence
)
)
comments: array(
0: // precedence
@@ -108,16 +93,10 @@ array(
expr: Expr_BinaryOp_Coalesce(
left: Expr_Variable(
name: a
comments: array(
0: // null coalesce
)
)
right: Expr_Variable(
name: b
)
comments: array(
0: // null coalesce
)
)
comments: array(
0: // null coalesce

@@ -0,0 +1,304 @@
Test formatting attributes
-----
<?php
0b1100;
0o14;
12;
0xc;
1_2_3_4_5_6;
3.141_592_653;
'foo';
"bar";
"foo
bar";
"foo\nbar";
"foo\nbar{$x}";
`foo\nbar`;
`foo\nbar{$x}`;
<<<'ABC'
ABC;
<<<'ABC'
foo bar
ABC;
<<<'ABC'
foo bar
ABC;
<<<ABC
foo\nbar
ABC;
<<<ABC
foo\nbar
ABC;
<<<ABC
foo\nbar{$x}baz
ABC;
<<<ABC
foo\nbar{$x}baz
ABC;
array();
[];
list($x) = $y;
[$x] = $y;
-----
!!attributes
array(
0: Stmt_Expression(
expr: Scalar_Int(
value: 12
rawValue: 0b1100
kind: KIND_BIN (2)
)
)
1: Stmt_Expression(
expr: Scalar_Int(
value: 12
rawValue: 0o14
kind: KIND_OCT (8)
)
)
2: Stmt_Expression(
expr: Scalar_Int(
value: 12
rawValue: 12
kind: KIND_DEC (10)
)
)
3: Stmt_Expression(
expr: Scalar_Int(
value: 12
rawValue: 0xc
kind: KIND_HEX (16)
)
)
4: Stmt_Expression(
expr: Scalar_Int(
value: 123456
rawValue: 1_2_3_4_5_6
kind: KIND_DEC (10)
)
)
5: Stmt_Expression(
expr: Scalar_Float(
value: 3.141592653
rawValue: 3.141_592_653
)
)
6: Stmt_Expression(
expr: Scalar_String(
value: foo
kind: KIND_SINGLE_QUOTED (1)
rawValue: 'foo'
)
)
7: Stmt_Expression(
expr: Scalar_String(
value: bar
kind: KIND_DOUBLE_QUOTED (2)
rawValue: "bar"
)
)
8: Stmt_Expression(
expr: Scalar_String(
value: foo
bar
kind: KIND_DOUBLE_QUOTED (2)
rawValue: "foo
bar"
)
)
9: Stmt_Expression(
expr: Scalar_String(
value: foo
bar
kind: KIND_DOUBLE_QUOTED (2)
rawValue: "foo\nbar"
)
)
10: Stmt_Expression(
expr: Scalar_InterpolatedString(
parts: array(
0: InterpolatedStringPart(
value: foo
bar
rawValue: foo\nbar
)
1: Expr_Variable(
name: x
)
)
kind: KIND_DOUBLE_QUOTED (2)
)
)
11: Stmt_Expression(
expr: Expr_ShellExec(
parts: array(
0: InterpolatedStringPart(
value: foo
bar
rawValue: foo\nbar
)
)
)
)
12: Stmt_Expression(
expr: Expr_ShellExec(
parts: array(
0: InterpolatedStringPart(
value: foo
bar
rawValue: foo\nbar
)
1: Expr_Variable(
name: x
)
)
)
)
13: Stmt_Expression(
expr: Scalar_String(
value:
kind: KIND_NOWDOC (4)
docLabel: ABC
docIndentation:
rawValue:
)
)
14: Stmt_Expression(
expr: Scalar_String(
value: foo bar
kind: KIND_NOWDOC (4)
docLabel: ABC
docIndentation:
rawValue: foo bar
)
)
15: Stmt_Expression(
expr: Scalar_String(
value: foo bar
kind: KIND_NOWDOC (4)
docLabel: ABC
docIndentation:
rawValue: foo bar
)
)
16: Stmt_Expression(
expr: Scalar_String(
value: foo
bar
kind: KIND_HEREDOC (3)
docLabel: ABC
docIndentation:
rawValue: foo\nbar
)
)
17: Stmt_Expression(
expr: Scalar_String(
value: foo
bar
kind: KIND_HEREDOC (3)
docLabel: ABC
docIndentation:
rawValue: foo\nbar
)
)
18: Stmt_Expression(
expr: Scalar_InterpolatedString(
parts: array(
0: InterpolatedStringPart(
value: foo
bar
rawValue: foo\nbar
)
1: Expr_Variable(
name: x
)
2: InterpolatedStringPart(
value: baz
rawValue: baz
)
)
kind: KIND_HEREDOC (3)
docLabel: ABC
docIndentation:
)
)
19: Stmt_Expression(
expr: Scalar_InterpolatedString(
parts: array(
0: InterpolatedStringPart(
value: foo
bar
rawValue: foo\nbar
)
1: Expr_Variable(
name: x
)
2: InterpolatedStringPart(
value: baz
rawValue: baz
)
)
kind: KIND_HEREDOC (3)
docLabel: ABC
docIndentation:
)
)
20: Stmt_Expression(
expr: Expr_Array(
items: array(
)
kind: KIND_LONG (1)
)
)
21: Stmt_Expression(
expr: Expr_Array(
items: array(
)
kind: KIND_SHORT (2)
)
)
22: Stmt_Expression(
expr: Expr_Assign(
var: Expr_List(
items: array(
0: ArrayItem(
key: null
value: Expr_Variable(
name: x
)
byRef: false
unpack: false
)
)
kind: KIND_LIST (1)
)
expr: Expr_Variable(
name: y
)
)
)
23: Stmt_Expression(
expr: Expr_Assign(
var: Expr_List(
items: array(
0: ArrayItem(
key: null
value: Expr_Variable(
name: x
)
byRef: false
unpack: false
)
)
kind: KIND_ARRAY (2)
)
expr: Expr_Variable(
name: y
)
)
)
)

@@ -19,9 +19,13 @@ array(
-----
!!positions
array(
0: Stmt_Nop[3:0 - 3:17](
comments: array(
0: /* comment */
0: Stmt_Block[2:1 - 4:1](
stmts: array(
0: Stmt_Nop[3:0 - 3:17](
comments: array(
0: /* comment */
)
)
)
)
)

@@ -75,7 +75,7 @@ array(
11: Stmt_Expression(
expr: Scalar_String(
value: !"!\!$!
!@@{ "\r" }@@!@@{ "\t" }@@!@@{ "\f" }@@!@@{ "\v" }@@!@@{ chr(27) /* "\e" */ }@@!\a
!@@{ "\r" }@@!@@{ "\t" }@@!@@{ "\f" }@@!@@{ "\v" }@@!@@{ chr(27) /* "\e" */ }@@!\a
)
)
12: Stmt_Expression(
@@ -83,4 +83,4 @@ array(
value: !@@{ chr(255) }@@!@@{ chr(255) }@@!@@{ chr(0) }@@!@@{ chr(0) }@@!
)
)
)
)

@@ -37,9 +37,6 @@ array(
0: Stmt_Expression(
expr: Scalar_String(
value:
comments: array(
0: // empty strings
)
)
comments: array(
0: // empty strings
@@ -53,9 +50,6 @@ array(
2: Stmt_Expression(
expr: Scalar_String(
value: Test '" $a \n
comments: array(
0: // constant encapsed strings
)
)
comments: array(
0: // constant encapsed strings
@@ -77,9 +71,6 @@ array(
name: a
)
)
comments: array(
0: // encapsed strings
)
)
comments: array(
0: // encapsed strings

@@ -155,12 +155,12 @@ array(
4: Stmt_Expression(
expr: Scalar_String(
value: a
b
b
c
c
d
e
d
e
)
)
5: Stmt_Expression(
@@ -168,7 +168,7 @@ array(
parts: array(
0: InterpolatedStringPart(
value: a
b
b
)
1: Expr_Variable(
@@ -176,8 +176,8 @@ array(
)
2: InterpolatedStringPart(
value:
d
e
d
e
)
)
)
@@ -185,15 +185,15 @@ array(
6: Stmt_Expression(
expr: Scalar_String(
value:
a
a
b
b
c
c
d
d
e
e
)
)
@@ -203,9 +203,9 @@ array(
0: InterpolatedStringPart(
value: a
@@{ "\t" }@@a
@@{ "\t" }@@a
b
b
)
@@ -215,9 +215,9 @@ array(
2: InterpolatedStringPart(
value:
d
d
e
e
)
)
@@ -321,7 +321,7 @@ array(
)
1: InterpolatedStringPart(
value:
-
-
)
)
)
@@ -334,7 +334,7 @@ array(
)
1: InterpolatedStringPart(
value:
-
-
)
)
)

@@ -67,8 +67,8 @@ array(
exprs: array(
0: Scalar_String(
value: a
b
c
b
c
)
)
)

@@ -76,10 +76,6 @@ array(
10: Stmt_Expression(
expr: Scalar_Float(
value: 1.844674407371E+19
comments: array(
0: // various integer -> float overflows
1: // (all are actually the same number, just in different representations)
)
)
comments: array(
0: // various integer -> float overflows

@@ -59,12 +59,6 @@ array(
expr: Expr_ConstFetch(
name: Name(
name: _100
comments: array(
0: // already a valid constant name
)
)
comments: array(
0: // already a valid constant name
)
)
comments: array(
@@ -74,9 +68,6 @@ array(
6: Stmt_Expression(
expr: Scalar_Int(
value: 100
comments: array(
0: // syntax errors
)
)
comments: array(
0: // syntax errors

@@ -3,8 +3,8 @@ Unicode escape sequence
<?php
"\u{0}";
"\u{114}";
"\u{1F602}";
"\u{114}$foo";
`\u{1F602}$bar`;
-----
array(
0: Stmt_Expression(
@@ -13,13 +13,66 @@ array(
)
)
1: Stmt_Expression(
expr: Scalar_String(
value: Ĕ
expr: Scalar_InterpolatedString(
parts: array(
0: InterpolatedStringPart(
value: Ĕ
)
1: Expr_Variable(
name: foo
)
)
)
)
2: Stmt_Expression(
expr: Expr_ShellExec(
parts: array(
0: InterpolatedStringPart(
value: @@{"\xF0\x9F\x98\x82"}@@
)
1: Expr_Variable(
name: bar
)
)
)
)
)
-----
<?php
"\u{0}";
"\u{114}$foo";
`\u{1F602}$bar`;
-----
!!version=5.6
array(
0: Stmt_Expression(
expr: Scalar_String(
value: @@{"\xF0\x9F\x98\x82"}@@
value: \u{0}
)
)
1: Stmt_Expression(
expr: Scalar_InterpolatedString(
parts: array(
0: InterpolatedStringPart(
value: \u{114}
)
1: Expr_Variable(
name: foo
)
)
)
)
2: Stmt_Expression(
expr: Expr_ShellExec(
parts: array(
0: InterpolatedStringPart(
value: \u{1F602}
)
1: Expr_Variable(
name: bar
)
)
)
)
)

@@ -42,6 +42,10 @@ array(
-----
Syntax error, unexpected T_STATIC, expecting T_STRING from 1:13 to 1:18
array(
0: Stmt_Block(
stmts: array(
)
)
)
-----
<?php class A extends self {}
@@ -211,6 +215,10 @@ array(
-----
Syntax error, unexpected T_STATIC, expecting T_STRING from 1:17 to 1:22
array(
0: Stmt_Block(
stmts: array(
)
)
)
-----
<?php interface A extends self {}

@@ -5,6 +5,10 @@ class ReadOnly {}
-----
Syntax error, unexpected T_READONLY, expecting T_STRING from 2:7 to 2:14
array(
0: Stmt_Block(
stmts: array(
)
)
)
-----
<?php

@@ -41,9 +41,11 @@ array(
name: a
)
)
6: Stmt_Throw(
expr: Expr_Variable(
name: e
6: Stmt_Expression(
expr: Expr_Throw(
expr: Expr_Variable(
name: e
)
)
)
7: Stmt_Label(

@@ -4,7 +4,9 @@ Declare
declare (X='Y');
declare (A='B', C='D') {}
declare (A='B', C='D') {
echo "foo";
}
declare (A='B', C='D'):
enddeclare;
@@ -43,6 +45,13 @@ array(
)
)
stmts: array(
0: Stmt_Echo(
exprs: array(
0: Scalar_String(
value: foo
)
)
)
)
)
2: Stmt_Declare(

@@ -83,9 +83,6 @@ array(
flags: 0
type: Name(
name: iterable
comments: array(
0: // PHP 7.0
)
)
byRef: false
variadic: false
@@ -103,9 +100,6 @@ array(
flags: 0
type: Name(
name: object
comments: array(
0: // PHP 7.1
)
)
byRef: false
variadic: false
@@ -123,9 +117,6 @@ array(
flags: 0
type: Name(
name: mixed
comments: array(
0: // PHP 7.2
)
)
byRef: false
variadic: false
@@ -143,9 +134,6 @@ array(
flags: 0
type: Name(
name: null
comments: array(
0: // PHP 8.0
)
)
byRef: false
variadic: false
@@ -163,9 +151,6 @@ array(
flags: 0
type: Name(
name: false
comments: array(
0: // PHP 8.0
)
)
byRef: false
variadic: false
@@ -292,9 +277,6 @@ array(
flags: 0
type: Name(
name: iterable
comments: array(
0: // PHP 7.0
)
)
byRef: false
variadic: false
@@ -312,9 +294,6 @@ array(
flags: 0
type: Name(
name: object
comments: array(
0: // PHP 7.1
)
)
byRef: false
variadic: false
@@ -332,9 +311,6 @@ array(
flags: 0
type: Name(
name: mixed
comments: array(
0: // PHP 7.2
)
)
byRef: false
variadic: false
@@ -352,9 +328,6 @@ array(
flags: 0
type: Name(
name: null
comments: array(
0: // PHP 8.0
)
)
byRef: false
variadic: false
@@ -372,9 +345,6 @@ array(
flags: 0
type: Name(
name: false
comments: array(
0: // PHP 8.0
)
)
byRef: false
variadic: false
@@ -501,9 +471,6 @@ array(
flags: 0
type: Identifier(
name: iterable
comments: array(
0: // PHP 7.0
)
)
byRef: false
variadic: false
@@ -521,9 +488,6 @@ array(
flags: 0
type: Name(
name: object
comments: array(
0: // PHP 7.1
)
)
byRef: false
variadic: false
@@ -541,9 +505,6 @@ array(
flags: 0
type: Name(
name: mixed
comments: array(
0: // PHP 7.2
)
)
byRef: false
variadic: false
@@ -561,9 +522,6 @@ array(
flags: 0
type: Name(
name: null
comments: array(
0: // PHP 8.0
)
)
byRef: false
variadic: false
@@ -581,9 +539,6 @@ array(
flags: 0
type: Name(
name: false
comments: array(
0: // PHP 8.0
)
)
byRef: false
variadic: false
@@ -710,9 +665,6 @@ array(
flags: 0
type: Identifier(
name: iterable
comments: array(
0: // PHP 7.0
)
)
byRef: false
variadic: false
@@ -730,9 +682,6 @@ array(
flags: 0
type: Identifier(
name: object
comments: array(
0: // PHP 7.1
)
)
byRef: false
variadic: false
@@ -750,9 +699,6 @@ array(
flags: 0
type: Name(
name: mixed
comments: array(
0: // PHP 7.2
)
)
byRef: false
variadic: false
@@ -770,9 +716,6 @@ array(
flags: 0
type: Name(
name: null
comments: array(
0: // PHP 8.0
)
)
byRef: false
variadic: false
@@ -790,9 +733,6 @@ array(
flags: 0
type: Name(
name: false
comments: array(
0: // PHP 8.0
)
)
byRef: false
variadic: false
@@ -919,9 +859,6 @@ array(
flags: 0
type: Identifier(
name: iterable
comments: array(
0: // PHP 7.0
)
)
byRef: false
variadic: false
@@ -939,9 +876,6 @@ array(
flags: 0
type: Identifier(
name: object
comments: array(
0: // PHP 7.1
)
)
byRef: false
variadic: false
@@ -959,9 +893,6 @@ array(
flags: 0
type: Identifier(
name: mixed
comments: array(
0: // PHP 7.2
)
)
byRef: false
variadic: false
@@ -979,9 +910,6 @@ array(
flags: 0
type: Identifier(
name: null
comments: array(
0: // PHP 8.0
)
)
byRef: false
variadic: false
@@ -999,9 +927,6 @@ array(
flags: 0
type: Identifier(
name: false
comments: array(
0: // PHP 8.0
)
)
byRef: false
variadic: false
@@ -1128,9 +1053,6 @@ array(
flags: 0
type: Identifier(
name: iterable
comments: array(
0: // PHP 7.0
)
)
byRef: false
variadic: false
@@ -1148,9 +1070,6 @@ array(
flags: 0
type: Identifier(
name: object
comments: array(
0: // PHP 7.1
)
)
byRef: false
variadic: false
@@ -1168,9 +1087,6 @@ array(
flags: 0
type: Identifier(
name: mixed
comments: array(
0: // PHP 7.2
)
)
byRef: false
variadic: false
@@ -1188,9 +1104,6 @@ array(
flags: 0
type: Identifier(
name: null
comments: array(
0: // PHP 8.0
)
)
byRef: false
variadic: false
@@ -1208,9 +1121,6 @@ array(
flags: 0
type: Identifier(
name: false
comments: array(
0: // PHP 8.0
)
)
byRef: false
variadic: false

@@ -47,9 +47,6 @@ array(
expr: Expr_Yield(
key: null
value: null
comments: array(
0: // statements
)
)
comments: array(
0: // statements
@@ -77,17 +74,11 @@ array(
expr: Expr_Assign(
var: Expr_Variable(
name: data
comments: array(
0: // expressions
)
)
expr: Expr_Yield(
key: null
value: null
)
comments: array(
0: // expressions
)
)
comments: array(
0: // expressions
@@ -214,9 +205,6 @@ array(
expr: Expr_FuncCall(
name: Name(
name: func
comments: array(
0: // yield in function calls
)
)
args: array(
0: Arg(
@@ -231,9 +219,6 @@ array(
unpack: false
)
)
comments: array(
0: // yield in function calls
)
)
comments: array(
0: // yield in function calls

@@ -28,6 +28,6 @@ array(
)
1: Stmt_HaltCompiler(
remaining:
Foo
Foo
)
)

@@ -63,17 +63,21 @@ array(
0: // Missing NS separator
)
)
1: Stmt_Expression(
expr: Expr_ConstFetch(
name: Name(
name: Bar
1: Stmt_Block(
stmts: array(
0: Stmt_Expression(
expr: Expr_ConstFetch(
name: Name(
name: Bar
)
)
)
)
)
2: Stmt_Expression(
expr: Expr_ConstFetch(
name: Name(
name: Baz
1: Stmt_Expression(
expr: Expr_ConstFetch(
name: Name(
name: Baz
)
)
)
)
)

@@ -0,0 +1,28 @@
Block statements
-----
<?php
echo "a";
{
}
{
echo "b";
}
if ($c) {
echo "c";
{
echo "d";
}
}
-----
echo "a";
{
}
{
echo "b";
}
if ($c) {
echo "c";
{
echo "d";
}
}