Compare commits

..

22 Commits

Author SHA1 Message Date
bd43ec7152 Release PHP-Parser 4.4.0 2020-04-10 18:34:50 +02:00
d86ca0f745 Support arbitrary expressions in new/instanceof 2020-02-22 21:09:03 +01:00
c8d1216531 Treat magic constants like normal constants 2020-02-22 21:06:03 +01:00
8999a13cb2 Make class constants fully dereferencable 2020-02-22 21:03:53 +01:00
c1eaa0d5cd Unify array and object dereferencability 2020-02-22 20:57:39 +01:00
ca5f7c9761 Split productions, unify {} dereferencing 2020-02-22 20:54:55 +01:00
ad696ee75d Make encapsed strings fully dereferencable 2020-02-22 20:49:11 +01:00
6770deda3d Check that ::class on object works 2020-02-22 20:42:11 +01:00
568236a305 Allow expressions in list()
Similar to the previous commit: list() syntactically accepts any
expression and non-variables are compile-time errors. The special
case of ($a) ends up being legal by accident.
2020-02-19 21:29:17 +01:00
9dda080a9d Allow expressions in isset()
Partial fix for #653. PHP 7 allows expressions inside isset(), but
rejects non-variables in the compiler. A side-effect of this is that
isset(($x)) is allowed, though this is not intentional.
2020-02-19 21:14:33 +01:00
3ec87ef757 Don't require doc comment to be last comment
Fixes #652.
2020-02-15 10:52:34 +01:00
a2443aaefa Make check in numeric literal separator emulator more precise
a) Don't check for !== false, preg_match() return 0 if there is
no match. This effectively means that the check was always true.
b) Check for hex characters only if there's an 0x prefix. 1_1 is
very likely a numeric separator, but a_b might well be part of
an identifier.

Fixes #639.
2020-02-09 22:50:19 +01:00
64f4d5b619 Add getProperty to ClassLike 2020-02-09 22:43:42 +01:00
f56d90d4f8 Add additional tests for modifying a Nop comment 2020-02-09 17:23:42 +01:00
40aa2282df Remove trailing comma 2020-02-09 17:15:59 +01:00
46cbd9393a Place Nop nodes more precisely
If the Nop is created to collect comments, place it directly after
the comment, instead of at the next non-whitespace character.
2020-02-09 17:10:33 +01:00
602af9060d Add end line / file position / token position to comments 2020-02-09 16:53:46 +01:00
bf086d9833 FPPP: Remove special Nop handling
This results in ugly formatting, but avoids generating invalid
code.
2020-02-09 16:17:12 +01:00
521addec91 Adding a test case for Nop causing bad new statement placement 2020-02-09 16:16:38 +01:00
88f3a669c1 Add union type to ParamBuilder and BuilderHelpers 2020-01-05 00:34:49 +01:00
ba9cf39999 Test on PHP 7.4 stable 2019-12-20 12:15:00 +01:00
f862853987 Add .gitattributes to .gitattributes
Alphasort too
2019-11-12 15:22:35 +01:00
32 changed files with 1789 additions and 1011 deletions

7
.gitattributes vendored
View File

@ -1,8 +1,9 @@
/doc export-ignore
/test export-ignore
/test_old export-ignore
/doc export-ignore
CHANGELOG.md export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.travis.yml export-ignore
CHANGELOG.md export-ignore
phpunit.xml.dist export-ignore
UPGRADE-*.md export-ignore
.gitignore export-ignore

View File

@ -1,6 +1,5 @@
language: php
dist: xenial
sudo: false
cache:
directories:
@ -11,7 +10,7 @@ php:
- 7.1
- 7.2
- 7.3
- 7.4snapshot
- 7.4
- nightly
install:

View File

@ -1,8 +1,27 @@
Version 4.3.1-dev
Version 4.4.1-dev
-----------------
Nothing yet.
Version 4.4.0 (2020-04-10)
--------------------------
### Added
* Added support for passing union types in builders.
* Added end line, token position and file position information for comments.
* Added `getProperty()` method to `ClassLike` nodes.
### Fixed
* Fixed generation of invalid code when using the formatting preserving pretty printer, and
inserting code next to certain nop statements. The formatting is still ugly though.
* `getDocComment()` no longer requires that the very last comment before a node be a doc comment.
There may not be non-doc comments between the doc comment and the declaration.
* Allowed arbitrary expressions in `isset()` and `list()`, rather than just variables.
In particular, this allows `isset(($x))`, which is legal PHP code.
* [PHP 8.0] Add support for [variable syntax tweaks RFC](https://wiki.php.net/rfc/variable_syntax_tweaks).
Version 4.3.0 (2019-11-08)
--------------------------

View File

@ -232,7 +232,7 @@ non_empty_statement:
| T_RETURN optional_expr semi { $$ = Stmt\Return_[$2]; }
| T_GLOBAL global_var_list semi { $$ = Stmt\Global_[$2]; }
| T_STATIC static_var_list semi { $$ = Stmt\Static_[$2]; }
| T_ECHO expr_list semi { $$ = Stmt\Echo_[$2]; }
| T_ECHO expr_list_forbid_comma semi { $$ = Stmt\Echo_[$2]; }
| T_INLINE_HTML { $$ = Stmt\InlineHTML[$1]; }
| expr semi { $$ = Stmt\Expression[$1]; }
| T_UNSET '(' variables_list ')' semi { $$ = Stmt\Unset_[$3]; }
@ -632,10 +632,14 @@ property_declaration:
| property_decl_name '=' expr { $$ = Stmt\PropertyProperty[$1, $3]; }
;
expr_list:
expr_list_forbid_comma:
non_empty_expr_list no_comma { $$ = $1; }
;
expr_list_allow_comma:
non_empty_expr_list optional_comma { $$ = $1; }
;
non_empty_expr_list:
non_empty_expr_list ',' expr { push($1, $3); }
| expr { init($1); }
@ -643,7 +647,7 @@ non_empty_expr_list:
for_expr:
/* empty */ { $$ = array(); }
| expr_list { $$ = $1; }
| expr_list_forbid_comma { $$ = $1; }
;
expr:
@ -706,7 +710,7 @@ expr:
| expr '?' expr ':' expr { $$ = Expr\Ternary[$1, $3, $5]; }
| expr '?' ':' expr { $$ = Expr\Ternary[$1, null, $4]; }
| expr T_COALESCE expr { $$ = Expr\BinaryOp\Coalesce[$1, $3]; }
| T_ISSET '(' variables_list ')' { $$ = Expr\Isset_[$3]; }
| T_ISSET '(' expr_list_allow_comma ')' { $$ = Expr\Isset_[$3]; }
| T_EMPTY '(' expr ')' { $$ = Expr\Empty_[$3]; }
| T_INCLUDE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_INCLUDE]; }
| T_INCLUDE_ONCE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_INCLUDE_ONCE]; }
@ -800,12 +804,13 @@ name:
class_name_reference:
class_name { $$ = $1; }
| new_variable { $$ = $1; }
| '(' expr ')' { $$ = $2; }
| error { $$ = Expr\Error[]; $this->errorState = 2; }
;
class_name_or_var:
class_name { $$ = $1; }
| dereferencable { $$ = $1; }
| fully_dereferencable { $$ = $1; }
;
exit_expr:
@ -827,9 +832,20 @@ ctor_arguments:
constant:
name { $$ = Expr\ConstFetch[$1]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex
| T_LINE { $$ = Scalar\MagicConst\Line[]; }
| T_FILE { $$ = Scalar\MagicConst\File[]; }
| T_DIR { $$ = Scalar\MagicConst\Dir[]; }
| T_CLASS_C { $$ = Scalar\MagicConst\Class_[]; }
| T_TRAIT_C { $$ = Scalar\MagicConst\Trait_[]; }
| T_METHOD_C { $$ = Scalar\MagicConst\Method[]; }
| T_FUNC_C { $$ = Scalar\MagicConst\Function_[]; }
| T_NS_C { $$ = Scalar\MagicConst\Namespace_[]; }
;
class_constant:
class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex
{ $$ = Expr\ClassConstFetch[$1, $3]; }
/* We interpret and isolated FOO:: as an unfinished class constant fetch. It could also be
/* We interpret an isolated FOO:: as an unfinished class constant fetch. It could also be
an unfinished static property fetch or unfinished scoped call. */
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM error
{ $$ = Expr\ClassConstFetch[$1, new Expr\Error(stackAttributes(#3))]; $this->errorState = 2; }
@ -849,28 +865,21 @@ dereferencable_scalar:
| T_CONSTANT_ENCAPSED_STRING
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
$$ = new Scalar\String_(Scalar\String_::parse($1), $attrs); }
| '"' encaps_list '"'
{ $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); }
;
scalar:
T_LNUMBER { $$ = $this->parseLNumber($1, attributes()); }
| T_DNUMBER { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; }
| T_LINE { $$ = Scalar\MagicConst\Line[]; }
| T_FILE { $$ = Scalar\MagicConst\File[]; }
| T_DIR { $$ = Scalar\MagicConst\Dir[]; }
| T_CLASS_C { $$ = Scalar\MagicConst\Class_[]; }
| T_TRAIT_C { $$ = Scalar\MagicConst\Trait_[]; }
| T_METHOD_C { $$ = Scalar\MagicConst\Method[]; }
| T_FUNC_C { $$ = Scalar\MagicConst\Function_[]; }
| T_NS_C { $$ = Scalar\MagicConst\Namespace_[]; }
| dereferencable_scalar { $$ = $1; }
| constant { $$ = $1; }
| class_constant { $$ = $1; }
| T_START_HEREDOC T_ENCAPSED_AND_WHITESPACE T_END_HEREDOC
{ $$ = $this->parseDocString($1, $2, $3, attributes(), stackAttributes(#3), true); }
| T_START_HEREDOC T_END_HEREDOC
{ $$ = $this->parseDocString($1, '', $2, attributes(), stackAttributes(#2), true); }
| '"' encaps_list '"'
{ $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); }
| T_START_HEREDOC encaps_list T_END_HEREDOC
{ $$ = $this->parseDocString($1, $2, $3, attributes(), stackAttributes(#3), true); }
;
@ -880,10 +889,16 @@ optional_expr:
| expr { $$ = $1; }
;
dereferencable:
fully_dereferencable:
variable { $$ = $1; }
| '(' expr ')' { $$ = $2; }
| dereferencable_scalar { $$ = $1; }
| class_constant { $$ = $1; }
;
array_object_dereferencable:
fully_dereferencable { $$ = $1; }
| constant { $$ = $1; }
;
callable_expr:
@ -894,18 +909,18 @@ callable_expr:
callable_variable:
simple_variable { $$ = Expr\Variable[$1]; }
| dereferencable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| constant '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| dereferencable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| array_object_dereferencable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| array_object_dereferencable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| function_call { $$ = $1; }
| dereferencable T_OBJECT_OPERATOR property_name argument_list
| array_object_dereferencable T_OBJECT_OPERATOR property_name argument_list
{ $$ = Expr\MethodCall[$1, $3, $4]; }
;
variable:
callable_variable { $$ = $1; }
| static_member { $$ = $1; }
| dereferencable T_OBJECT_OPERATOR property_name { $$ = Expr\PropertyFetch[$1, $3]; }
| array_object_dereferencable T_OBJECT_OPERATOR property_name
{ $$ = Expr\PropertyFetch[$1, $3]; }
;
simple_variable:
@ -950,22 +965,7 @@ property_name:
;
list_expr:
T_LIST '(' list_expr_elements ')' { $$ = Expr\List_[$3]; }
;
list_expr_elements:
list_expr_elements ',' list_expr_element { push($1, $3); }
| list_expr_element { init($1); }
;
list_expr_element:
variable { $$ = Expr\ArrayItem[$1, null, false]; }
| '&' variable { $$ = Expr\ArrayItem[$2, null, true]; }
| list_expr { $$ = Expr\ArrayItem[$1, null, false]; }
| expr T_DOUBLE_ARROW variable { $$ = Expr\ArrayItem[$3, $1, false]; }
| expr T_DOUBLE_ARROW '&' variable { $$ = Expr\ArrayItem[$4, $1, true]; }
| expr T_DOUBLE_ARROW list_expr { $$ = Expr\ArrayItem[$3, $1, false]; }
| /* empty */ { $$ = null; }
T_LIST '(' inner_array_pair_list ')' { $$ = Expr\List_[$3]; }
;
array_pair_list:
@ -985,10 +985,12 @@ inner_array_pair_list:
;
array_pair:
expr T_DOUBLE_ARROW expr { $$ = Expr\ArrayItem[$3, $1, false]; }
| expr { $$ = Expr\ArrayItem[$1, null, false]; }
| expr T_DOUBLE_ARROW '&' variable { $$ = Expr\ArrayItem[$4, $1, true]; }
expr { $$ = Expr\ArrayItem[$1, null, false]; }
| '&' variable { $$ = Expr\ArrayItem[$2, null, true]; }
| list_expr { $$ = Expr\ArrayItem[$1, null, false]; }
| expr T_DOUBLE_ARROW expr { $$ = Expr\ArrayItem[$3, $1, false]; }
| expr T_DOUBLE_ARROW '&' variable { $$ = Expr\ArrayItem[$4, $1, true]; }
| expr T_DOUBLE_ARROW list_expr { $$ = Expr\ArrayItem[$3, $1, false]; }
| T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; }
| /* empty */ { $$ = null; }
;

View File

@ -178,7 +178,7 @@ function resolveMacros($code) {
return '$startAttributes = ' . $args[1] . ';'
. ' if (isset($startAttributes[\'comments\']))'
. ' { ' . $args[0] . ' = new Stmt\Nop($this->createZeroLengthAttributes($startAttributes)); }'
. ' { ' . $args[0] . ' = new Stmt\Nop($this->createCommentNopAttributes($startAttributes[\'comments\'])); }'
. ' else { ' . $args[0] . ' = null; }';
}

View File

@ -44,7 +44,7 @@ class Param implements PhpParser\Builder
/**
* Sets type for the parameter.
*
* @param string|Node\Name|Node\NullableType $type Parameter type
* @param string|Node\Name|Node\NullableType|Node\UnionType $type Parameter type
*
* @return $this The builder instance (for fluid interface)
*/
@ -60,7 +60,7 @@ class Param implements PhpParser\Builder
/**
* Sets type for the parameter.
*
* @param string|Node\Name|Node\NullableType $type Parameter type
* @param string|Node\Name|Node\NullableType|Node\UnionType $type Parameter type
*
* @return $this The builder instance (for fluid interface)
*

View File

@ -8,6 +8,7 @@ use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;
use PhpParser\Node\UnionType;
/**
* This class defines helpers used in the implementation of builders. Don't use it directly.
@ -158,16 +159,19 @@ final class BuilderHelpers
* In particular, builtin types become Identifiers, custom types become Names and nullables
* are wrapped in NullableType nodes.
*
* @param string|Name|Identifier|NullableType $type The type to normalize
* @param string|Name|Identifier|NullableType|UnionType $type The type to normalize
*
* @return Name|Identifier|NullableType The normalized type
* @return Name|Identifier|NullableType|UnionType The normalized type
*/
public static function normalizeType($type) {
if (!is_string($type)) {
if (!$type instanceof Name && !$type instanceof Identifier
&& !$type instanceof NullableType) {
if (
!$type instanceof Name && !$type instanceof Identifier &&
!$type instanceof NullableType && !$type instanceof UnionType
) {
throw new \LogicException(
'Type must be a string, or an instance of Name, Identifier or NullableType');
'Type must be a string, or an instance of Name, Identifier, NullableType or UnionType'
);
}
return $type;
}
@ -193,7 +197,7 @@ final class BuilderHelpers
throw new \LogicException('void type cannot be nullable');
}
return $nullable ? new Node\NullableType($type) : $type;
return $nullable ? new NullableType($type) : $type;
}
/**

View File

@ -5,9 +5,12 @@ namespace PhpParser;
class Comment implements \JsonSerializable
{
protected $text;
protected $line;
protected $filePos;
protected $tokenPos;
protected $startLine;
protected $startFilePos;
protected $startTokenPos;
protected $endLine;
protected $endFilePos;
protected $endTokenPos;
/**
* Constructs a comment node.
@ -18,12 +21,17 @@ class Comment implements \JsonSerializable
* @param int $startTokenPos Token offset the comment started on
*/
public function __construct(
string $text, int $startLine = -1, int $startFilePos = -1, int $startTokenPos = -1
string $text,
int $startLine = -1, int $startFilePos = -1, int $startTokenPos = -1,
int $endLine = -1, int $endFilePos = -1, int $endTokenPos = -1
) {
$this->text = $text;
$this->line = $startLine;
$this->filePos = $startFilePos;
$this->tokenPos = $startTokenPos;
$this->startLine = $startLine;
$this->startFilePos = $startFilePos;
$this->startTokenPos = $startTokenPos;
$this->endLine = $endLine;
$this->endFilePos = $endFilePos;
$this->endTokenPos = $endTokenPos;
}
/**
@ -38,28 +46,88 @@ class Comment implements \JsonSerializable
/**
* Gets the line number the comment started on.
*
* @return int Line number
* @return int Line number (or -1 if not available)
*/
public function getLine() : int {
return $this->line;
public function getStartLine() : int {
return $this->startLine;
}
/**
* Gets the file offset the comment started on.
*
* @return int File offset
* @return int File offset (or -1 if not available)
*/
public function getFilePos() : int {
return $this->filePos;
public function getStartFilePos() : int {
return $this->startFilePos;
}
/**
* Gets the token offset the comment started on.
*
* @return int Token offset (or -1 if not available)
*/
public function getStartTokenPos() : int {
return $this->startTokenPos;
}
/**
* Gets the line number the comment ends on.
*
* @return int Line number (or -1 if not available)
*/
public function getEndLine() : int {
return $this->endLine;
}
/**
* Gets the file offset the comment ends on.
*
* @return int File offset (or -1 if not available)
*/
public function getEndFilePos() : int {
return $this->endFilePos;
}
/**
* Gets the token offset the comment ends on.
*
* @return int Token offset (or -1 if not available)
*/
public function getEndTokenPos() : int {
return $this->endTokenPos;
}
/**
* Gets the line number the comment started on.
*
* @deprecated Use getStartLine() instead
*
* @return int Line number
*/
public function getLine() : int {
return $this->startLine;
}
/**
* Gets the file offset the comment started on.
*
* @deprecated Use getStartFilePos() instead
*
* @return int File offset
*/
public function getFilePos() : int {
return $this->startFilePos;
}
/**
* Gets the token offset the comment started on.
*
* @deprecated Use getStartTokenPos() instead
*
* @return int Token offset
*/
public function getTokenPos() : int {
return $this->tokenPos;
return $this->startTokenPos;
}
/**
@ -159,9 +227,13 @@ class Comment implements \JsonSerializable
return [
'nodeType' => $type,
'text' => $this->text,
'line' => $this->line,
'filePos' => $this->filePos,
'tokenPos' => $this->tokenPos,
// TODO: Rename these to include "start".
'line' => $this->startLine,
'filePos' => $this->startFilePos,
'tokenPos' => $this->startTokenPos,
'endLine' => $this->endLine,
'endFilePos' => $this->endFilePos,
'endTokenPos' => $this->endTokenPos,
];
}
}

View File

@ -73,7 +73,9 @@ class JsonDecoder
}
return new $className(
$value['text'], $value['line'] ?? -1, $value['filePos'] ?? -1, $value['tokenPos'] ?? -1
$value['text'],
$value['line'] ?? -1, $value['filePos'] ?? -1, $value['tokenPos'] ?? -1,
$value['endLine'] ?? -1, $value['endFilePos'] ?? -1, $value['endTokenPos'] ?? -1
);
}

View File

@ -300,17 +300,23 @@ class Lexer
$this->line += substr_count($value, "\n");
$this->filePos += \strlen($value);
} else {
$origLine = $this->line;
$origFilePos = $this->filePos;
$this->line += substr_count($token[1], "\n");
$this->filePos += \strlen($token[1]);
if (\T_COMMENT === $token[0] || \T_DOC_COMMENT === $token[0]) {
if ($this->attributeCommentsUsed) {
$comment = \T_DOC_COMMENT === $token[0]
? new Comment\Doc($token[1], $this->line, $this->filePos, $this->pos)
: new Comment($token[1], $this->line, $this->filePos, $this->pos);
? new Comment\Doc($token[1],
$origLine, $origFilePos, $this->pos,
$this->line, $this->filePos - 1, $this->pos)
: new Comment($token[1],
$origLine, $origFilePos, $this->pos,
$this->line, $this->filePos - 1, $this->pos);
$startAttributes['comments'][] = $comment;
}
}
$this->line += substr_count($token[1], "\n");
$this->filePos += \strlen($token[1]);
continue;
}

View File

@ -21,7 +21,8 @@ final class NumericLiteralSeparatorEmulator implements TokenEmulatorInterface
return false;
}
return preg_match('~[0-9a-f]_[0-9a-f]~i', $code) !== false;
return preg_match('~[0-9]_[0-9]~', $code)
|| preg_match('~0x[0-9a-f]+_[0-9a-f]~i', $code);
}
public function emulate(string $code, array $tokens): array

View File

@ -95,8 +95,6 @@ interface Node
/**
* Gets the doc comment of the node.
*
* The doc comment has to be the last comment associated with the node.
*
* @return null|Comment\Doc Doc comment object or null
*/
public function getDocComment();

View File

@ -53,6 +53,26 @@ abstract class ClassLike extends Node\Stmt
return $properties;
}
/**
* Gets property with the given name defined directly in this class/interface/trait.
*
* @param string $name Name of the property
*
* @return Property|null Property node or null if the property does not exist
*/
public function getProperty(string $name) {
foreach ($this->stmts as $stmt) {
if ($stmt instanceof Property) {
foreach ($stmt->props as $prop) {
if ($prop instanceof PropertyProperty && $name === $prop->name->toString()) {
return $stmt;
}
}
}
}
return null;
}
/**
* Gets all methods defined directly in this class/interface/trait
*

View File

@ -108,22 +108,18 @@ abstract class NodeAbstract implements Node, \JsonSerializable
/**
* Gets the doc comment of the node.
*
* The doc comment has to be the last comment associated with the node.
*
* @return null|Comment\Doc Doc comment object or null
*/
public function getDocComment() {
$comments = $this->getComments();
if (!$comments) {
return null;
for ($i = count($comments) - 1; $i >= 0; $i--) {
$comment = $comments[$i];
if ($comment instanceof Comment\Doc) {
return $comment;
}
}
$lastComment = $comments[count($comments) - 1];
if (!$lastComment instanceof Comment\Doc) {
return null;
}
return $lastComment;
return null;
}
/**
@ -135,16 +131,17 @@ abstract class NodeAbstract implements Node, \JsonSerializable
*/
public function setDocComment(Comment\Doc $docComment) {
$comments = $this->getComments();
$numComments = count($comments);
if ($numComments > 0 && $comments[$numComments - 1] instanceof Comment\Doc) {
// Replace existing doc comment
$comments[$numComments - 1] = $docComment;
} else {
// Append new comment
$comments[] = $docComment;
for ($i = count($comments) - 1; $i >= 0; $i--) {
if ($comments[$i] instanceof Comment\Doc) {
// Replace existing doc comment.
$comments[$i] = $docComment;
$this->setAttribute('comments', $comments);
return;
}
}
// Append new doc comment.
$comments[] = $docComment;
$this->setAttribute('comments', $comments);
}

View File

@ -944,7 +944,7 @@ class Php5 extends \PhpParser\ParserAbstract
$this->semValue = array();
},
4 => function ($stackPos) {
$startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createZeroLengthAttributes($startAttributes)); } else { $nop = null; };
$startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); } else { $nop = null; };
if ($nop !== null) { $this->semStack[$stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$stackPos-(1-1)];
},
5 => function ($stackPos) {
@ -1317,7 +1317,7 @@ class Php5 extends \PhpParser\ParserAbstract
$this->semValue = array();
},
126 => function ($stackPos) {
$startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createZeroLengthAttributes($startAttributes)); } else { $nop = null; };
$startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); } else { $nop = null; };
if ($nop !== null) { $this->semStack[$stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$stackPos-(1-1)];
},
127 => function ($stackPos) {
@ -1715,7 +1715,7 @@ class Php5 extends \PhpParser\ParserAbstract
$this->semValue = array();
},
255 => function ($stackPos) {
$startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createZeroLengthAttributes($startAttributes)); } else { $nop = null; };
$startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); } else { $nop = null; };
if ($nop !== null) { $this->semStack[$stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$stackPos-(1-1)];
},
256 => function ($stackPos) {

File diff suppressed because it is too large Load Diff

View File

@ -842,21 +842,29 @@ abstract class ParserAbstract implements Parser
}
/**
* Create attributes for a zero-length node with the given start attributes.
* Create attributes for a zero-length common-capturing nop.
*
* @param array $startAttributes
* @param Comment[] $comments
* @return array
*/
protected function createZeroLengthAttributes(array $startAttributes) {
$attributes = $startAttributes;
if (isset($startAttributes['startLine'])) {
$attributes['endLine'] = $startAttributes['startLine'];
protected function createCommentNopAttributes(array $comments) {
$comment = $comments[count($comments) - 1];
$commentEndLine = $comment->getEndLine();
$commentEndFilePos = $comment->getEndFilePos();
$commentEndTokenPos = $comment->getEndTokenPos();
$attributes = ['comments' => $comments];
if (-1 !== $commentEndLine) {
$attributes['startLine'] = $commentEndLine;
$attributes['endLine'] = $commentEndLine;
}
if (isset($startAttributes['startTokenPos'])) {
$attributes['endTokenPos'] = $startAttributes['startTokenPos'] - 1;
if (-1 !== $commentEndFilePos) {
$attributes['startFilePos'] = $commentEndFilePos + 1;
$attributes['endFilePos'] = $commentEndFilePos;
}
if (isset($startAttributes['startFilePos'])) {
$attributes['endFilePos'] = $startAttributes['startFilePos'] - 1;
if (-1 !== $commentEndTokenPos) {
$attributes['startTokenPos'] = $commentEndTokenPos + 1;
$attributes['endTokenPos'] = $commentEndTokenPos;
}
return $attributes;
}

View File

@ -756,20 +756,13 @@ abstract class PrettyPrinterAbstract
$itemEndPos = $origArrItem->getEndTokenPos();
\assert($itemStartPos >= 0 && $itemEndPos >= 0);
if ($itemEndPos < $itemStartPos) {
// End can be before start for Nop nodes, because offsets refer to non-whitespace
// locations, which for an "empty" node might result in an inverted order.
assert($origArrItem instanceof Stmt\Nop);
continue;
}
$origIndentLevel = $this->indentLevel;
$lastElemIndentLevel = $this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment;
$this->setIndentLevel($lastElemIndentLevel);
$comments = $arrItem->getComments();
$origComments = $origArrItem->getComments();
$commentStartPos = $origComments ? $origComments[0]->getTokenPos() : $itemStartPos;
$commentStartPos = $origComments ? $origComments[0]->getStartTokenPos() : $itemStartPos;
\assert($commentStartPos >= 0);
$commentsChanged = $comments !== $origComments;

View File

@ -80,6 +80,8 @@ class ParamTest extends \PHPUnit\Framework\TestCase
/**
* @dataProvider provideTestTypes
* @dataProvider provideTestNullableTypes
* @dataProvider provideTestUnionTypes
*/
public function testTypes($typeHint, $expectedType) {
$node = $this->createParamBuilder('test')
@ -114,9 +116,14 @@ class ParamTest extends \PHPUnit\Framework\TestCase
['Some\Class', new Node\Name('Some\Class')],
['\Foo', new Node\Name\FullyQualified('Foo')],
['self', new Node\Name('self')],
[new Node\Name('Some\Class'), new Node\Name('Some\Class')],
];
}
public function provideTestNullableTypes() {
return [
['?array', new Node\NullableType(new Node\Identifier('array'))],
['?Some\Class', new Node\NullableType(new Node\Name('Some\Class'))],
[new Node\Name('Some\Class'), new Node\Name('Some\Class')],
[
new Node\NullableType(new Node\Identifier('int')),
new Node\NullableType(new Node\Identifier('int'))
@ -128,6 +135,33 @@ class ParamTest extends \PHPUnit\Framework\TestCase
];
}
public function provideTestUnionTypes() {
return [
[
new Node\UnionType([
new Node\Name('Some\Class'),
new Node\Identifier('array'),
]),
new Node\UnionType([
new Node\Name('Some\Class'),
new Node\Identifier('array'),
]),
],
[
new Node\UnionType([
new Node\Identifier('self'),
new Node\Identifier('array'),
new Node\Name\FullyQualified('Foo')
]),
new Node\UnionType([
new Node\Identifier('self'),
new Node\Identifier('array'),
new Node\Name\FullyQualified('Foo')
]),
],
];
}
public function testVoidTypeError() {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Parameter type cannot be void');
@ -136,7 +170,7 @@ class ParamTest extends \PHPUnit\Framework\TestCase
public function testInvalidTypeError() {
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Type must be a string, or an instance of Name, Identifier or NullableType');
$this->expectExceptionMessage('Type must be a string, or an instance of Name, Identifier, NullableType or UnionType');
$this->createParamBuilder('test')->setType(new \stdClass);
}

View File

@ -4,14 +4,21 @@ namespace PhpParser;
class CommentTest extends \PHPUnit\Framework\TestCase
{
public function testGetSet() {
$comment = new Comment('/* Some comment */', 1, 10, 2);
public function testGetters() {
$comment = new Comment('/* Some comment */',
1, 10, 2, 1, 27, 2);
$this->assertSame('/* Some comment */', $comment->getText());
$this->assertSame('/* Some comment */', (string) $comment);
$this->assertSame(1, $comment->getLine());
$this->assertSame(10, $comment->getFilePos());
$this->assertSame(2, $comment->getTokenPos());
$this->assertSame(1, $comment->getStartLine());
$this->assertSame(10, $comment->getStartFilePos());
$this->assertSame(2, $comment->getStartTokenPos());
$this->assertSame(1, $comment->getEndLine());
$this->assertSame(27, $comment->getEndFilePos());
$this->assertSame(2, $comment->getEndTokenPos());
}
/**

View File

@ -103,7 +103,9 @@ class LexerTest extends \PHPUnit\Framework\TestCase
[
'startLine' => 3,
'comments' => [
new Comment\Doc('/** doc' . "\n" . 'comment */', 2, 14, 5),
new Comment\Doc('/** doc' . "\n" . 'comment */',
2, 14, 5,
3, 31, 5),
]
],
['endLine' => 3]
@ -120,10 +122,14 @@ class LexerTest extends \PHPUnit\Framework\TestCase
[
'startLine' => 2,
'comments' => [
new Comment('/* comment */', 1, 6, 1),
new Comment('// comment' . "\n", 1, 20, 3),
new Comment\Doc('/** docComment 1 */', 2, 31, 4),
new Comment\Doc('/** docComment 2 */', 2, 50, 5),
new Comment('/* comment */',
1, 6, 1, 1, 18, 1),
new Comment('// comment' . "\n",
1, 20, 3, 2, 30, 3),
new Comment\Doc('/** docComment 1 */',
2, 31, 4, 2, 49, 4),
new Comment\Doc('/** docComment 2 */',
2, 50, 5, 2, 68, 5),
],
],
['endLine' => 2]

View File

@ -94,6 +94,31 @@ class ClassTest extends \PHPUnit\Framework\TestCase
$this->assertSame($properties, $class->getProperties());
}
public function testGetProperty() {
$properties = [
$fooProp = new Property(Class_::MODIFIER_PUBLIC, [new PropertyProperty('foo1')]),
$barProp = new Property(Class_::MODIFIER_PUBLIC, [new PropertyProperty('BAR1')]),
$fooBarProp = new Property(Class_::MODIFIER_PUBLIC, [new PropertyProperty('foo2'), new PropertyProperty('bar2')]),
];
$class = new Class_('Foo', [
'stmts' => [
new TraitUse([]),
$properties[0],
new ClassConst([]),
$properties[1],
new ClassMethod('fooBar'),
$properties[2],
]
]);
$this->assertSame($fooProp, $class->getProperty('foo1'));
$this->assertSame($barProp, $class->getProperty('BAR1'));
$this->assertSame($fooBarProp, $class->getProperty('foo2'));
$this->assertSame($fooBarProp, $class->getProperty('bar2'));
$this->assertNull($class->getProperty('bar1'));
$this->assertNull($class->getProperty('nonExisting'));
}
public function testGetMethod() {
$methodConstruct = new ClassMethod('__CONSTRUCT');
$methodTest = new ClassMethod('test');

View File

@ -34,8 +34,9 @@ class NodeAbstractTest extends \PHPUnit\Framework\TestCase
'startFilePos' => 14,
'endFilePos' => 15,
'comments' => [
new Comment('// Comment' . "\n"),
new Comment('// Comment 1' . "\n"),
new Comment\Doc('/** doc comment */'),
new Comment('// Comment 2' . "\n"),
],
];
@ -79,12 +80,12 @@ class NodeAbstractTest extends \PHPUnit\Framework\TestCase
$this->assertSame('/** doc comment */', $node->getDocComment()->getText());
$comments = $node->getComments();
array_pop($comments); // remove doc comment
array_splice($comments, 1, 1, []); // remove doc comment
$node->setAttribute('comments', $comments);
$this->assertNull($node->getDocComment());
array_pop($comments); // remove comment
$node->setAttribute('comments', $comments);
// Remove all comments.
$node->setAttribute('comments', []);
$this->assertNull($node->getDocComment());
}
@ -108,6 +109,12 @@ class NodeAbstractTest extends \PHPUnit\Framework\TestCase
$node->setAttribute('comments', [$c1, $c2]);
$node->setDocComment($docComment);
$this->assertSame([$c1, $c2, $docComment], $node->getAttribute('comments'));
// Replace doc comment that is not at the end.
$newDocComment = new Comment\Doc('/** new baz */');
$node->setAttribute('comments', [$c1, $docComment, $c2]);
$node->setDocComment($newDocComment);
$this->assertSame([$c1, $newDocComment, $c2], $node->getAttribute('comments'));
}
/**
@ -301,14 +308,20 @@ PHP;
"text": "\/\/ comment\n",
"line": 2,
"filePos": 6,
"tokenPos": 1
"tokenPos": 1,
"endLine": 3,
"endFilePos": 16,
"endTokenPos": 1
},
{
"nodeType": "Comment_Doc",
"text": "\/** doc comment *\/",
"line": 3,
"filePos": 17,
"tokenPos": 2
"tokenPos": 2,
"endLine": 3,
"endFilePos": 34,
"endTokenPos": 2
}
],
"endLine": 6

View File

@ -60,7 +60,8 @@ EOC;
$this->assertInstanceOf(Stmt\Function_::class, $fn);
$this->assertEquals([
'comments' => [
new Comment\Doc('/** Doc comment */', 2, 6, 1),
new Comment\Doc('/** Doc comment */',
2, 6, 1, 2, 23, 1),
],
'startLine' => 3,
'endLine' => 7,
@ -82,8 +83,10 @@ EOC;
$this->assertInstanceOf(Stmt\Echo_::class, $echo);
$this->assertEquals([
'comments' => [
new Comment("// Line\n", 4, 49, 12),
new Comment("// Comments\n", 5, 61, 14),
new Comment("// Line\n",
4, 49, 12, 5, 56, 12),
new Comment("// Comments\n",
5, 61, 14, 6, 72, 14),
],
'startLine' => 6,
'endLine' => 6,

View File

@ -0,0 +1,76 @@
Adding statement to Class Method containing Nop
-----
<?php
class Foo {
public function __construct()
{
// I'm just a comment
}
}
-----
$stmts[0]->stmts[0]->stmts[] = new Stmt\Expression(new Node\Expr\Variable('foo'));
-----
<?php
class Foo {
public function __construct()
{
// I'm just a comment
$foo; }
}
-----
<?php
class Foo {
public function __construct()
{
/* I'm just a comment */
}
}
-----
$stmts[0]->stmts[0]->stmts[] = new Stmt\Expression(new Node\Expr\Variable('foo'));
-----
<?php
class Foo {
public function __construct()
{
/* I'm just a comment */
$foo;
}
}
-----
<?php
class Foo {
public function __construct()
{
/* I'm just a comment */
}
}
-----
$stmts[0]->stmts[0]->stmts[0]->setAttribute('comments', [new Comment("/* I'm a new comment */")]);
-----
<?php
class Foo {
public function __construct()
{
/* I'm a new comment */
}
}
-----
<?php
class Foo {
public function __construct()
{
// I'm just a comment
}
}
-----
$stmts[0]->stmts[0]->stmts[0]->setAttribute('comments', [new Comment("// I'm a new comment\n")]);
-----
<?php
class Foo {
public function __construct()
{
// I'm a new comment
}
}

View File

@ -0,0 +1,49 @@
Expressions in isset()
-----
<?php
// This is legal.
isset(($a), (($b)));
// This is illegal, but not a syntax error.
isset(1 + 1);
-----
!!php7
array(
0: Stmt_Expression(
expr: Expr_Isset(
vars: array(
0: Expr_Variable(
name: a
)
1: Expr_Variable(
name: b
)
)
comments: array(
0: // This is legal.
)
)
comments: array(
0: // This is legal.
)
)
1: Stmt_Expression(
expr: Expr_Isset(
vars: array(
0: Expr_BinaryOp_Plus(
left: Scalar_LNumber(
value: 1
)
right: Scalar_LNumber(
value: 1
)
)
)
comments: array(
0: // This is illegal, but not a syntax error.
)
)
comments: array(
0: // This is illegal, but not a syntax error.
)
)
)

View File

@ -0,0 +1,81 @@
Expressions in list()
-----
<?php
// This is legal.
list(($a), ((($b)))) = $x;
// This is illegal, but not a syntax error.
list(1 + 1) = $x;
-----
!!php7
array(
0: Stmt_Expression(
expr: Expr_Assign(
var: Expr_List(
items: array(
0: Expr_ArrayItem(
key: null
value: Expr_Variable(
name: a
)
byRef: false
unpack: false
)
1: Expr_ArrayItem(
key: null
value: Expr_Variable(
name: b
)
byRef: false
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.
)
)
1: Stmt_Expression(
expr: Expr_Assign(
var: Expr_List(
items: array(
0: Expr_ArrayItem(
key: null
value: Expr_BinaryOp_Plus(
left: Scalar_LNumber(
value: 1
)
right: Scalar_LNumber(
value: 1
)
)
byRef: false
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.
)
)
)

View File

@ -5,6 +5,8 @@ Constant fetches
A;
A::B;
A::class;
$a::B;
$a::class;
-----
array(
0: Stmt_Expression(
@ -40,4 +42,24 @@ array(
)
)
)
3: Stmt_Expression(
expr: Expr_ClassConstFetch(
class: Expr_Variable(
name: a
)
name: Identifier(
name: B
)
)
)
4: Stmt_Expression(
expr: Expr_ClassConstFetch(
class: Expr_Variable(
name: a
)
name: Identifier(
name: class
)
)
)
)

View File

@ -0,0 +1,292 @@
Dereferencing of constants
-----
<?php
A->length;
A->length();
A[0];
A[0][1][2];
A{0};
A::B[0];
A::B[0][1][2];
A::B{0};
A::B->length;
A::B->length();
A::B::C;
A::B::$c;
A::B::c();
__FUNCTION__[0];
__FUNCTION__->length;
__FUNCIONT__->length();
-----
!!php7
array(
0: Stmt_Expression(
expr: Expr_PropertyFetch(
var: Expr_ConstFetch(
name: Name(
parts: array(
0: A
)
)
)
name: Identifier(
name: length
)
)
)
1: Stmt_Expression(
expr: Expr_MethodCall(
var: Expr_ConstFetch(
name: Name(
parts: array(
0: A
)
)
)
name: Identifier(
name: length
)
args: array(
)
)
)
2: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_ConstFetch(
name: Name(
parts: array(
0: A
)
)
)
dim: Scalar_LNumber(
value: 0
)
)
)
3: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_ArrayDimFetch(
var: Expr_ArrayDimFetch(
var: Expr_ConstFetch(
name: Name(
parts: array(
0: A
)
)
)
dim: Scalar_LNumber(
value: 0
)
)
dim: Scalar_LNumber(
value: 1
)
)
dim: Scalar_LNumber(
value: 2
)
)
)
4: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_ConstFetch(
name: Name(
parts: array(
0: A
)
)
)
dim: Scalar_LNumber(
value: 0
)
)
)
5: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_ClassConstFetch(
class: Name(
parts: array(
0: A
)
)
name: Identifier(
name: B
)
)
dim: Scalar_LNumber(
value: 0
)
)
)
6: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_ArrayDimFetch(
var: Expr_ArrayDimFetch(
var: Expr_ClassConstFetch(
class: Name(
parts: array(
0: A
)
)
name: Identifier(
name: B
)
)
dim: Scalar_LNumber(
value: 0
)
)
dim: Scalar_LNumber(
value: 1
)
)
dim: Scalar_LNumber(
value: 2
)
)
)
7: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_ClassConstFetch(
class: Name(
parts: array(
0: A
)
)
name: Identifier(
name: B
)
)
dim: Scalar_LNumber(
value: 0
)
)
)
8: Stmt_Expression(
expr: Expr_PropertyFetch(
var: Expr_ClassConstFetch(
class: Name(
parts: array(
0: A
)
)
name: Identifier(
name: B
)
)
name: Identifier(
name: length
)
)
)
9: Stmt_Expression(
expr: Expr_MethodCall(
var: Expr_ClassConstFetch(
class: Name(
parts: array(
0: A
)
)
name: Identifier(
name: B
)
)
name: Identifier(
name: length
)
args: array(
)
)
)
10: Stmt_Expression(
expr: Expr_ClassConstFetch(
class: Expr_ClassConstFetch(
class: Name(
parts: array(
0: A
)
)
name: Identifier(
name: B
)
)
name: Identifier(
name: C
)
)
)
11: Stmt_Expression(
expr: Expr_StaticPropertyFetch(
class: Expr_ClassConstFetch(
class: Name(
parts: array(
0: A
)
)
name: Identifier(
name: B
)
)
name: VarLikeIdentifier(
name: c
)
)
)
12: Stmt_Expression(
expr: Expr_StaticCall(
class: Expr_ClassConstFetch(
class: Name(
parts: array(
0: A
)
)
name: Identifier(
name: B
)
)
name: Identifier(
name: c
)
args: array(
)
)
)
13: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Scalar_MagicConst_Function(
)
dim: Scalar_LNumber(
value: 0
)
)
)
14: Stmt_Expression(
expr: Expr_PropertyFetch(
var: Scalar_MagicConst_Function(
)
name: Identifier(
name: length
)
)
)
15: Stmt_Expression(
expr: Expr_MethodCall(
var: Expr_ConstFetch(
name: Name(
parts: array(
0: __FUNCIONT__
)
)
)
name: Identifier(
name: length
)
args: array(
)
)
)
)

View File

@ -2,24 +2,36 @@ Uniform variable syntax in PHP 7 (misc)
-----
<?php
A::A[0];
A::A[0][1][2];
"string"->length();
"foo$bar"[0];
"foo$bar"->length();
(clone $obj)->b[0](1);
[0, 1][0] = 1;
-----
!!php7
array(
0: Stmt_Expression(
expr: Expr_MethodCall(
var: Scalar_String(
value: string
)
name: Identifier(
name: length
)
args: array(
)
)
)
1: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_ClassConstFetch(
class: Name(
parts: array(
0: A
var: Scalar_Encapsed(
parts: array(
0: Scalar_EncapsedStringPart(
value: foo
)
1: Expr_Variable(
name: bar
)
)
name: Identifier(
name: A
)
)
dim: Scalar_LNumber(
@ -27,37 +39,17 @@ array(
)
)
)
1: Stmt_Expression(
expr: Expr_ArrayDimFetch(
var: Expr_ArrayDimFetch(
var: Expr_ArrayDimFetch(
var: Expr_ClassConstFetch(
class: Name(
parts: array(
0: A
)
)
name: Identifier(
name: A
)
)
dim: Scalar_LNumber(
value: 0
)
)
dim: Scalar_LNumber(
value: 1
)
)
dim: Scalar_LNumber(
value: 2
)
)
)
2: Stmt_Expression(
expr: Expr_MethodCall(
var: Scalar_String(
value: string
var: Scalar_Encapsed(
parts: array(
0: Scalar_EncapsedStringPart(
value: foo
)
1: Expr_Variable(
name: bar
)
)
)
name: Identifier(
name: length

View File

@ -0,0 +1,61 @@
Arbitrary expressions in new and instanceof
-----
<?php
new ('Foo' . $bar);
new ('Foo' . $bar)($arg);
$obj instanceof ('Foo' . $bar);
-----
!!php7
array(
0: Stmt_Expression(
expr: Expr_New(
class: Expr_BinaryOp_Concat(
left: Scalar_String(
value: Foo
)
right: Expr_Variable(
name: bar
)
)
args: array(
)
)
)
1: Stmt_Expression(
expr: Expr_New(
class: Expr_BinaryOp_Concat(
left: Scalar_String(
value: Foo
)
right: Expr_Variable(
name: bar
)
)
args: array(
0: Arg(
value: Expr_Variable(
name: arg
)
byRef: false
unpack: false
)
)
)
)
2: Stmt_Expression(
expr: Expr_Instanceof(
expr: Expr_Variable(
name: obj
)
class: Expr_BinaryOp_Concat(
left: Scalar_String(
value: Foo
)
right: Expr_Variable(
name: bar
)
)
)
)
)

View File

@ -10,4 +10,18 @@ array(
0: /* Comment */
)
)
)
-----
<?php
{
/* comment */
}
-----
!!positions
array(
0: Stmt_Nop[3:0 - 3:17](
comments: array(
0: /* comment */
)
)
)