Compare commits

...

30 Commits

Author SHA1 Message Date
579f4ce846 Release PHP-Parser 3.1.3 2017-12-26 15:43:21 +01:00
94ca9a7ab9 Use Tokens::class in lexer
Ref #441.
2017-11-13 01:14:55 +01:00
bac91b426e Correctly determine Type of Node when PHP-Parser's namespaces are prefixed
Hi there,

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

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

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

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

Code:

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

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

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

`ScoperAbc123\PHPParser\Node\Stmt\Declare_` -> `Stmt_Declare`
2017-11-12 21:11:41 +01:00
08131e7ff2 Release PHP-Parser 3.1.2 2017-11-04 12:48:34 +01:00
0ba710affa Add setDocComment() to namespace build (#437) 2017-11-04 12:43:02 +01:00
72231abe6d ClassMethod stmts property can be null (#435)
Conflicts:
	lib/PhpParser/Node/Stmt/ClassMethod.php
2017-11-04 12:38:45 +01:00
d418bf3951 Preserve comments on empty blocks (#382) 2017-10-01 16:54:43 +02:00
5a9fbca54a Add attribute for namespace kinds (#417)
One of KIND_SEMICOLON or KIND_BRACED.
2017-09-26 18:45:05 +02:00
a1e8e1a30e Release PHP-Parser 3.1.1 2017-09-02 19:10:46 +02:00
d77e6cd6e9 Allow TraitUse statements in trait builder
Fixes #413.
2017-08-29 23:18:59 +02:00
a10780ca0d Handle Nop statement after brace-style namespace
Fixes #412.
2017-08-29 23:14:27 +02:00
4d4896e553 Release PHP-Parser 3.1.0 2017-07-28 16:45:09 +02:00
6fa073879e Remove deprecation of Name::$parts
It doesn't look like this is going away for now, and we currently
don't have the APIs to cover all existing usages elegantly.
2017-07-19 17:13:10 +02:00
5a3a1ec25c Handle LC_NUMERIC with comma decimal separator
Closes #399.
2017-07-19 17:10:55 +02:00
4a7d011317 Add support for object type 2017-06-28 23:12:13 +02:00
7f862ac21c Add support for trailing comma in group use 2017-06-28 23:05:28 +02:00
0808939f81 Release PHP-Parser 3.0.6 2017-06-28 22:53:48 +02:00
7bfc320bda Switch to dist: trusty, so HHVM runs 2017-06-03 15:25:50 +02:00
bc0bff3f87 Fix method name in tests 2017-06-03 15:18:23 +02:00
c28b8556f5 Fix Lexer errorHandling when there is no tokens
I encounter an issue when no tokens exist. An error `Undefined offset -1` is triggered.
2017-06-03 15:17:45 +02:00
3da86df48f Deprecate Node::setLine() 2017-04-29 12:58:35 +02:00
901b895c02 Fix spelling of VISIBILITY_MODIFIER_MASK 2017-04-19 11:20:05 +02:00
c877c1a64f Add Builder\Param::makeVariadic() 2017-04-19 11:16:29 +02:00
c3cbf07946 Pretty printer: Preserve comments in arrays and calls
If call arguments or array contains comments, print it in multiline
form, so that comments may be preserved.
2017-04-09 19:49:47 +02:00
2b9e2f71b7 Release PHP-Parser 3.0.5 2017-03-05 19:23:57 +01:00
d5873b177b Adjust the end attributes on the stack as well 2017-02-26 23:45:14 +01:00
48ec654d0c Make Expr\Error nodes empty
Resolves issue #359.
2017-02-26 23:38:32 +01:00
c12a4c8239 Fix start attribute assignment for Error in ClassConstFetch 2017-02-26 23:00:38 +01:00
86ea6fe8c4 Remove leftover code 2017-02-26 22:53:08 +01:00
1b59e918f7 Perform NullableType resolution earlier
This makes sure function signatures are already fully resolved in
enterNode(). Resolves issue #360.
2017-02-26 22:50:31 +01:00
36 changed files with 1649 additions and 1227 deletions

View File

@ -1,5 +1,5 @@
language: php language: php
dist: trusty
sudo: false sudo: false
cache: cache:

View File

@ -1,8 +1,85 @@
Version 3.0.5-dev Version 3.1.4-dev
----------------- -----------------
Nothing yet. Nothing yet.
Version 3.1.3 (2017-12-26)
--------------------------
### Fixed
* Improve compatibility with php-scoper, by supporting prefixed namespaces in
`NodeAbstract::getType()`.
Version 3.1.2 (2017-11-04)
--------------------------
### Fixed
* Comments on empty blocks are now preserved on a `Stmt\Nop` node. (#382)
### Added
* Added `kind` attribute for `Stmt\Namespace_` node, which is one of `KIND_SEMICOLON` or
`KIND_BRACED`. (#417)
* Added `setDocComment()` method to namespace builder. (#437)
Version 3.1.1 (2017-09-02)
--------------------------
### Fixed
* Fixed syntax error on comment after brace-style namespace declaration. (#412)
* Added support for TraitUse statements in trait builder. (#413)
Version 3.1.0 (2017-07-28)
--------------------------
### Added
* [PHP 7.2] Added support for trailing comma in group use statements.
* [PHP 7.2] Added support for `object` type. This means `object` types will now be represented as a
builtin type (a simple `"object"` string), rather than a class `Name`.
### Fixed
* Floating-point numbers are now printed correctly if the LC_NUMERIC locale uses a comma as decimal
separator.
### Changed
* `Name::$parts` is no longer deprecated.
Version 3.0.6 (2017-06-28)
--------------------------
### Fixed
* Fixed the spelling of `Class_::VISIBILITY_MODIFIER_MASK`. The previous spelling of
`Class_::VISIBILITY_MODIFER_MASK` is preserved for backwards compatibility.
* The pretty printing will now preserve comments inside array literals and function calls by
printing the array items / function arguments on separate lines. Array literals and functions that
do not contain comments are not affected.
### Added
* Added `Builder\Param::makeVariadic()`.
### Deprecated
* The `Node::setLine()` method has been deprecated.
Version 3.0.5 (2017-03-05)
--------------------------
### Fixed
* Name resolution of `NullableType`s is now performed earlier, so that a fully resolved signature is
available when a function is entered. (#360)
* `Error` nodes are now considered empty, while previously they extended until the token where the
error occurred. This made some nodes larger than expected. (#359)
* Fixed notices being thrown during error recovery in some situations. (#362)
Version 3.0.4 (2017-02-10) Version 3.0.4 (2017-02-10)
-------------------------- --------------------------

View File

@ -56,11 +56,17 @@ top_statement:
| T_HALT_COMPILER | T_HALT_COMPILER
{ $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; } { $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; }
| T_NAMESPACE namespace_name ';' | T_NAMESPACE namespace_name ';'
{ $$ = Stmt\Namespace_[$2, null]; $this->checkNamespace($$); } { $$ = Stmt\Namespace_[$2, null];
$$->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON);
$this->checkNamespace($$); }
| T_NAMESPACE namespace_name '{' top_statement_list '}' | T_NAMESPACE namespace_name '{' top_statement_list '}'
{ $$ = Stmt\Namespace_[$2, $4]; $this->checkNamespace($$); } { $$ = Stmt\Namespace_[$2, $4];
$$->setAttribute('kind', Stmt\Namespace_::KIND_BRACED);
$this->checkNamespace($$); }
| T_NAMESPACE '{' top_statement_list '}' | T_NAMESPACE '{' top_statement_list '}'
{ $$ = Stmt\Namespace_[null, $3]; $this->checkNamespace($$); } { $$ = Stmt\Namespace_[null, $3];
$$->setAttribute('kind', Stmt\Namespace_::KIND_BRACED);
$this->checkNamespace($$); }
| T_USE use_declarations ';' { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; } | T_USE use_declarations ';' { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; }
| T_USE use_type use_declarations ';' { $$ = Stmt\Use_[$3, $2]; } | T_USE use_type use_declarations ';' { $$ = Stmt\Use_[$3, $2]; }
| group_use_declaration ';' { $$ = $1; } | group_use_declaration ';' { $$ = $1; }
@ -155,7 +161,15 @@ inner_statement:
; ;
non_empty_statement: non_empty_statement:
'{' inner_statement_list '}' { $$ = $2; prependLeadingComments($$); } '{' inner_statement_list '}'
{
if ($2) {
$$ = $2; prependLeadingComments($$);
} else {
makeNop($$, $this->startAttributeStack[#1]);
if (null === $$) { $$ = array(); }
}
}
| T_IF parentheses_expr statement elseif_list else_single | T_IF parentheses_expr statement elseif_list else_single
{ $$ = Stmt\If_[$2, ['stmts' => toArray($3), 'elseifs' => $4, 'else' => $5]]; } { $$ = Stmt\If_[$2, ['stmts' => toArray($3), 'elseifs' => $4, 'else' => $5]]; }
| T_IF parentheses_expr ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';' | T_IF parentheses_expr ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';'

View File

@ -59,6 +59,10 @@ no_comma:
| ',' { $this->emitError(new Error('A trailing comma is not allowed here', attributes())); } | ',' { $this->emitError(new Error('A trailing comma is not allowed here', attributes())); }
; ;
optional_comma:
/* empty */
| ','
top_statement: top_statement:
statement { $$ = $1; } statement { $$ = $1; }
| function_declaration_statement { $$ = $1; } | function_declaration_statement { $$ = $1; }
@ -66,11 +70,17 @@ top_statement:
| T_HALT_COMPILER | T_HALT_COMPILER
{ $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; } { $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; }
| T_NAMESPACE namespace_name semi | T_NAMESPACE namespace_name semi
{ $$ = Stmt\Namespace_[$2, null]; $this->checkNamespace($$); } { $$ = Stmt\Namespace_[$2, null];
$$->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON);
$this->checkNamespace($$); }
| T_NAMESPACE namespace_name '{' top_statement_list '}' | T_NAMESPACE namespace_name '{' top_statement_list '}'
{ $$ = Stmt\Namespace_[$2, $4]; $this->checkNamespace($$); } { $$ = Stmt\Namespace_[$2, $4];
$$->setAttribute('kind', Stmt\Namespace_::KIND_BRACED);
$this->checkNamespace($$); }
| T_NAMESPACE '{' top_statement_list '}' | T_NAMESPACE '{' top_statement_list '}'
{ $$ = Stmt\Namespace_[null, $3]; $this->checkNamespace($$); } { $$ = Stmt\Namespace_[null, $3];
$$->setAttribute('kind', Stmt\Namespace_::KIND_BRACED);
$this->checkNamespace($$); }
| T_USE use_declarations semi { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; } | T_USE use_declarations semi { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; }
| T_USE use_type use_declarations semi { $$ = Stmt\Use_[$3, $2]; } | T_USE use_type use_declarations semi { $$ = Stmt\Use_[$3, $2]; }
| group_use_declaration semi { $$ = $1; } | group_use_declaration semi { $$ = $1; }
@ -95,7 +105,7 @@ group_use_declaration:
; ;
unprefixed_use_declarations: unprefixed_use_declarations:
non_empty_unprefixed_use_declarations no_comma { $$ = $1; } non_empty_unprefixed_use_declarations optional_comma { $$ = $1; }
; ;
non_empty_unprefixed_use_declarations: non_empty_unprefixed_use_declarations:
@ -114,7 +124,7 @@ non_empty_use_declarations:
; ;
inline_use_declarations: inline_use_declarations:
non_empty_inline_use_declarations no_comma { $$ = $1; } non_empty_inline_use_declarations optional_comma { $$ = $1; }
; ;
non_empty_inline_use_declarations: non_empty_inline_use_declarations:
@ -187,7 +197,15 @@ inner_statement:
; ;
non_empty_statement: non_empty_statement:
'{' inner_statement_list '}' { $$ = $2; prependLeadingComments($$); } '{' inner_statement_list '}'
{
if ($2) {
$$ = $2; prependLeadingComments($$);
} else {
makeNop($$, $this->startAttributeStack[#1]);
if (null === $$) { $$ = array(); }
}
}
| T_IF '(' expr ')' statement elseif_list else_single | T_IF '(' expr ')' statement elseif_list else_single
{ $$ = Stmt\If_[$3, ['stmts' => toArray($5), 'elseifs' => $6, 'else' => $7]]; } { $$ = Stmt\If_[$3, ['stmts' => toArray($5), 'elseifs' => $6, 'else' => $7]]; }
| T_IF '(' expr ')' ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';' | T_IF '(' expr ')' ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';'
@ -763,7 +781,7 @@ constant:
/* We interpret and isolated FOO:: as an unfinished class constant fetch. It could also be /* We interpret and isolated FOO:: as an unfinished class constant fetch. It could also be
an unfinished static property fetch or unfinished scoped call. */ an unfinished static property fetch or unfinished scoped call. */
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM error | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM error
{ $$ = Expr\ClassConstFetch[$1, Expr\Error[]]; $this->errorState = 2; } { $$ = Expr\ClassConstFetch[$1, new Expr\Error(stackAttributes(#3))]; $this->errorState = 2; }
; ;
array_short_syntax: array_short_syntax:

View File

@ -205,7 +205,7 @@ function resolveMacros($code) {
assertArgs(1, $args, $name); assertArgs(1, $args, $name);
return '$attrs = $this->startAttributeStack[#1]; $stmts = ' . $args[0] . '; ' return '$attrs = $this->startAttributeStack[#1]; $stmts = ' . $args[0] . '; '
. 'if (!empty($attrs[\'comments\']) && isset($stmts[0])) {' . 'if (!empty($attrs[\'comments\'])) {'
. '$stmts[0]->setAttribute(\'comments\', ' . '$stmts[0]->setAttribute(\'comments\', '
. 'array_merge($attrs[\'comments\'], $stmts[0]->getAttribute(\'comments\', []))); }'; . 'array_merge($attrs[\'comments\'], $stmts[0]->getAttribute(\'comments\', []))); }';
} }

View File

@ -6,7 +6,7 @@ use PhpParser;
use PhpParser\Node; use PhpParser\Node;
use PhpParser\Node\Stmt; use PhpParser\Node\Stmt;
class Namespace_ extends PhpParser\BuilderAbstract class Namespace_ extends Declaration
{ {
private $name; private $name;
private $stmts = array(); private $stmts = array();
@ -33,27 +33,12 @@ class Namespace_ extends PhpParser\BuilderAbstract
return $this; return $this;
} }
/**
* Adds multiple statements.
*
* @param array $stmts The statements to add
*
* @return $this The builder instance (for fluid interface)
*/
public function addStmts(array $stmts) {
foreach ($stmts as $stmt) {
$this->addStmt($stmt);
}
return $this;
}
/** /**
* Returns the built node. * Returns the built node.
* *
* @return Node The built node * @return Node The built node
*/ */
public function getNode() { public function getNode() {
return new Stmt\Namespace_($this->name, $this->stmts); return new Stmt\Namespace_($this->name, $this->stmts, $this->attributes);
} }
} }

View File

@ -16,6 +16,8 @@ class Param extends PhpParser\BuilderAbstract
protected $byRef = false; protected $byRef = false;
protected $variadic = false;
/** /**
* Creates a parameter builder. * Creates a parameter builder.
* *
@ -65,6 +67,17 @@ class Param extends PhpParser\BuilderAbstract
return $this; return $this;
} }
/**
* Make the parameter variadic
*
* @return $this The builder instance (for fluid interface)
*/
public function makeVariadic() {
$this->variadic = true;
return $this;
}
/** /**
* Returns the built parameter node. * Returns the built parameter node.
* *
@ -72,7 +85,7 @@ class Param extends PhpParser\BuilderAbstract
*/ */
public function getNode() { public function getNode() {
return new Node\Param( return new Node\Param(
$this->name, $this->default, $this->type, $this->byRef $this->name, $this->default, $this->type, $this->byRef, $this->variadic
); );
} }
} }

View File

@ -8,6 +8,7 @@ use PhpParser\Node\Stmt;
class Trait_ extends Declaration class Trait_ extends Declaration
{ {
protected $name; protected $name;
protected $uses = array();
protected $properties = array(); protected $properties = array();
protected $methods = array(); protected $methods = array();
@ -34,6 +35,8 @@ class Trait_ extends Declaration
$this->properties[] = $stmt; $this->properties[] = $stmt;
} else if ($stmt instanceof Stmt\ClassMethod) { } else if ($stmt instanceof Stmt\ClassMethod) {
$this->methods[] = $stmt; $this->methods[] = $stmt;
} else if ($stmt instanceof Stmt\TraitUse) {
$this->uses[] = $stmt;
} else { } else {
throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType())); throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType()));
} }
@ -49,7 +52,7 @@ class Trait_ extends Declaration
public function getNode() { public function getNode() {
return new Stmt\Trait_( return new Stmt\Trait_(
$this->name, array( $this->name, array(
'stmts' => array_merge($this->properties, $this->methods) 'stmts' => array_merge($this->uses, $this->properties, $this->methods)
), $this->attributes ), $this->attributes
); );
} }

View File

@ -80,7 +80,7 @@ abstract class BuilderAbstract implements Builder {
} }
$builtinTypes = array( $builtinTypes = array(
'array', 'callable', 'string', 'int', 'float', 'bool', 'iterable', 'void' 'array', 'callable', 'string', 'int', 'float', 'bool', 'iterable', 'void', 'object'
); );
$lowerType = strtolower($type); $lowerType = strtolower($type);

View File

@ -185,6 +185,7 @@ class Lexer
return; return;
} }
if (count($this->tokens) > 0) {
// Check for unterminated comment // Check for unterminated comment
$lastToken = $this->tokens[count($this->tokens) - 1]; $lastToken = $this->tokens[count($this->tokens) - 1];
if ($this->isUnterminatedComment($lastToken)) { if ($this->isUnterminatedComment($lastToken)) {
@ -196,6 +197,7 @@ class Lexer
])); ]));
} }
} }
}
/** /**
* Fetches the next token. * Fetches the next token.
@ -358,7 +360,7 @@ class Lexer
if ('T_HASHBANG' === $name) { if ('T_HASHBANG' === $name) {
// HHVM uses a special token for #! hashbang lines // HHVM uses a special token for #! hashbang lines
$tokenMap[$i] = Tokens::T_INLINE_HTML; $tokenMap[$i] = Tokens::T_INLINE_HTML;
} else if (defined($name = 'PhpParser\Parser\Tokens::' . $name)) { } else if (defined($name = Tokens::class . '::' . $name)) {
// Other tokens can be mapped directly // Other tokens can be mapped directly
$tokenMap[$i] = constant($name); $tokenMap[$i] = constant($name);
} }

View File

@ -29,6 +29,8 @@ interface Node
* Sets line the node started in. * Sets line the node started in.
* *
* @param int $line Line * @param int $line Line
*
* @deprecated
*/ */
public function setLine($line); public function setLine($line);

View File

@ -8,7 +8,6 @@ class Name extends NodeAbstract
{ {
/** /**
* @var string[] Parts of the name * @var string[] Parts of the name
* @deprecated Avoid directly accessing $parts, use methods instead.
*/ */
public $parts; public $parts;

View File

@ -30,7 +30,7 @@ class ClassConst extends Node\Stmt
public function isPublic() { public function isPublic() {
return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0 return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0
|| ($this->flags & Class_::VISIBILITY_MODIFER_MASK) === 0; || ($this->flags & Class_::VISIBILITY_MODIFIER_MASK) === 0;
} }
public function isProtected() { public function isProtected() {

View File

@ -17,7 +17,7 @@ class ClassMethod extends Node\Stmt implements FunctionLike
public $params; public $params;
/** @var null|string|Node\Name|Node\NullableType Return type */ /** @var null|string|Node\Name|Node\NullableType Return type */
public $returnType; public $returnType;
/** @var Node[] Statements */ /** @var Node[]|null Statements */
public $stmts; public $stmts;
/** @deprecated Use $flags instead */ /** @deprecated Use $flags instead */
@ -69,7 +69,7 @@ class ClassMethod extends Node\Stmt implements FunctionLike
public function isPublic() { public function isPublic() {
return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0 return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0
|| ($this->flags & Class_::VISIBILITY_MODIFER_MASK) === 0; || ($this->flags & Class_::VISIBILITY_MODIFIER_MASK) === 0;
} }
public function isProtected() { public function isProtected() {

View File

@ -14,7 +14,9 @@ class Class_ extends ClassLike
const MODIFIER_ABSTRACT = 16; const MODIFIER_ABSTRACT = 16;
const MODIFIER_FINAL = 32; const MODIFIER_FINAL = 32;
const VISIBILITY_MODIFER_MASK = 7; // 1 | 2 | 4 const VISIBILITY_MODIFIER_MASK = 7; // 1 | 2 | 4
/** @deprecated */
const VISIBILITY_MODIFER_MASK = self::VISIBILITY_MODIFIER_MASK;
/** @var int Type */ /** @var int Type */
public $flags; public $flags;
@ -74,7 +76,7 @@ class Class_ extends ClassLike
* @internal * @internal
*/ */
public static function verifyModifier($a, $b) { public static function verifyModifier($a, $b) {
if ($a & self::VISIBILITY_MODIFER_MASK && $b & self::VISIBILITY_MODIFER_MASK) { if ($a & self::VISIBILITY_MODIFIER_MASK && $b & self::VISIBILITY_MODIFIER_MASK) {
throw new Error('Multiple access type modifiers are not allowed'); throw new Error('Multiple access type modifiers are not allowed');
} }

View File

@ -6,6 +6,10 @@ use PhpParser\Node;
class Namespace_ extends Node\Stmt class Namespace_ extends Node\Stmt
{ {
/* For use in the "kind" attribute */
const KIND_SEMICOLON = 1;
const KIND_BRACED = 2;
/** @var null|Node\Name Name */ /** @var null|Node\Name Name */
public $name; public $name;
/** @var Node[] Statements */ /** @var Node[] Statements */

View File

@ -34,7 +34,7 @@ class Property extends Node\Stmt
public function isPublic() { public function isPublic() {
return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0 return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0
|| ($this->flags & Class_::VISIBILITY_MODIFER_MASK) === 0; || ($this->flags & Class_::VISIBILITY_MODIFIER_MASK) === 0;
} }
public function isProtected() { public function isProtected() {

View File

@ -21,7 +21,15 @@ abstract class NodeAbstract implements Node, \JsonSerializable
* @return string Type of the node * @return string Type of the node
*/ */
public function getType() { public function getType() {
return strtr(substr(rtrim(get_class($this), '_'), 15), '\\', '_'); $className = rtrim(get_class($this), '_');
return strtr(
substr(
$className,
strpos($className, 'PhpParser\Node') + 15
),
'\\',
'_'
);
} }
/** /**
@ -37,6 +45,8 @@ abstract class NodeAbstract implements Node, \JsonSerializable
* Sets line the node started in. * Sets line the node started in.
* *
* @param int $line Line * @param int $line Line
*
* @deprecated
*/ */
public function setLine($line) { public function setLine($line) {
$this->setAttribute('startLine', (int) $line); $this->setAttribute('startLine', (int) $line);

View File

@ -120,10 +120,6 @@ class NameResolver extends NodeVisitorAbstract
} }
} }
} }
} elseif ($node instanceof Node\NullableType) {
if ($node->type instanceof Name) {
$node->type = $this->resolveClassName($node->type);
}
} }
} }
@ -172,13 +168,20 @@ class NameResolver extends NodeVisitorAbstract
/** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure $node */ /** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure $node */
private function resolveSignature($node) { private function resolveSignature($node) {
foreach ($node->params as $param) { foreach ($node->params as $param) {
if ($param->type instanceof Name) { $param->type = $this->resolveType($param->type);
$param->type = $this->resolveClassName($param->type);
} }
$node->returnType = $this->resolveType($node->returnType);
} }
if ($node->returnType instanceof Name) {
$node->returnType = $this->resolveClassName($node->returnType); private function resolveType($node) {
if ($node instanceof Node\NullableType) {
$node->type = $this->resolveType($node->type);
return $node;
} }
if ($node instanceof Name) {
return $this->resolveClassName($node);
}
return $node;
} }
protected function resolveClassName(Name $name) { protected function resolveClassName(Name $name) {

View File

@ -1257,15 +1257,21 @@ class Php5 extends \PhpParser\ParserAbstract
} }
protected function reduceRule89() { protected function reduceRule89() {
$this->semValue = new Stmt\Namespace_($this->semStack[$this->stackPos-(3-2)], null, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes); $this->checkNamespace($this->semValue); $this->semValue = new Stmt\Namespace_($this->semStack[$this->stackPos-(3-2)], null, $this->startAttributeStack[$this->stackPos-(3-1)] + $this->endAttributes);
$this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON);
$this->checkNamespace($this->semValue);
} }
protected function reduceRule90() { protected function reduceRule90() {
$this->semValue = new Stmt\Namespace_($this->semStack[$this->stackPos-(5-2)], $this->semStack[$this->stackPos-(5-4)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes); $this->checkNamespace($this->semValue); $this->semValue = new Stmt\Namespace_($this->semStack[$this->stackPos-(5-2)], $this->semStack[$this->stackPos-(5-4)], $this->startAttributeStack[$this->stackPos-(5-1)] + $this->endAttributes);
$this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED);
$this->checkNamespace($this->semValue);
} }
protected function reduceRule91() { protected function reduceRule91() {
$this->semValue = new Stmt\Namespace_(null, $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes); $this->checkNamespace($this->semValue); $this->semValue = new Stmt\Namespace_(null, $this->semStack[$this->stackPos-(4-3)], $this->startAttributeStack[$this->stackPos-(4-1)] + $this->endAttributes);
$this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED);
$this->checkNamespace($this->semValue);
} }
protected function reduceRule92() { protected function reduceRule92() {
@ -1410,7 +1416,14 @@ class Php5 extends \PhpParser\ParserAbstract
} }
protected function reduceRule127() { protected function reduceRule127() {
$this->semValue = $this->semStack[$this->stackPos-(3-2)]; $attrs = $this->startAttributeStack[$this->stackPos-(3-1)]; $stmts = $this->semValue; if (!empty($attrs['comments']) && isset($stmts[0])) {$stmts[0]->setAttribute('comments', array_merge($attrs['comments'], $stmts[0]->getAttribute('comments', []))); };
if ($this->semStack[$this->stackPos-(3-2)]) {
$this->semValue = $this->semStack[$this->stackPos-(3-2)]; $attrs = $this->startAttributeStack[$this->stackPos-(3-1)]; $stmts = $this->semValue; if (!empty($attrs['comments'])) {$stmts[0]->setAttribute('comments', array_merge($attrs['comments'], $stmts[0]->getAttribute('comments', []))); };
} else {
$startAttributes = $this->startAttributeStack[$this->stackPos-(3-1)]; if (isset($startAttributes['comments'])) { $this->semValue = new Stmt\Nop(['comments' => $startAttributes['comments']]); } else { $this->semValue = null; };
if (null === $this->semValue) { $this->semValue = array(); }
}
} }
protected function reduceRule128() { protected function reduceRule128() {

File diff suppressed because it is too large Load Diff

View File

@ -315,7 +315,11 @@ abstract class ParserAbstract implements Parser
//$this->traceShift($this->errorSymbol); //$this->traceShift($this->errorSymbol);
++$this->stackPos; ++$this->stackPos;
$stateStack[$this->stackPos] = $state = $action; $stateStack[$this->stackPos] = $state = $action;
$this->endAttributes = $this->endAttributeStack[$this->stackPos];
// We treat the error symbol as being empty, so we reset the end attributes
// to the end attributes of the last non-error symbol
$this->endAttributeStack[$this->stackPos] = $this->endAttributeStack[$this->stackPos - 1];
$this->endAttributes = $this->endAttributeStack[$this->stackPos - 1];
break; break;
case 3: case 3:
@ -441,6 +445,7 @@ abstract class ParserAbstract implements Parser
if ($stmt instanceof Node\Stmt\Namespace_) { if ($stmt instanceof Node\Stmt\Namespace_) {
$afterFirstNamespace = true; $afterFirstNamespace = true;
} elseif (!$stmt instanceof Node\Stmt\HaltCompiler } elseif (!$stmt instanceof Node\Stmt\HaltCompiler
&& !$stmt instanceof Node\Stmt\Nop
&& $afterFirstNamespace && !$hasErrored) { && $afterFirstNamespace && !$hasErrored) {
$this->emitError(new Error( $this->emitError(new Error(
'No code may exist outside of namespace {}', $stmt->getAttributes())); 'No code may exist outside of namespace {}', $stmt->getAttributes()));
@ -525,6 +530,7 @@ abstract class ParserAbstract implements Parser
'string' => true, 'string' => true,
'iterable' => true, 'iterable' => true,
'void' => true, 'void' => true,
'object' => true,
]; ];
if (!$name->isUnqualified()) { if (!$name->isUnqualified()) {

View File

@ -182,6 +182,11 @@ class Standard extends PrettyPrinterAbstract
$stringValue = sprintf('%.17G', $node->value); $stringValue = sprintf('%.17G', $node->value);
} }
// %G is locale dependent and there exists no locale-independent alternative. We don't want
// mess with switching locales here, so let's assume that a comma is the only non-standard
// decimal separator we may encounter...
$stringValue = str_replace(',', '.', $stringValue);
// ensure that number is really printed as float // ensure that number is really printed as float
return preg_match('/^-?[0-9]+$/', $stringValue) ? $stringValue . '.0' : $stringValue; return preg_match('/^-?[0-9]+$/', $stringValue) ? $stringValue . '.0' : $stringValue;
} }
@ -438,12 +443,12 @@ class Standard extends PrettyPrinterAbstract
protected function pExpr_FuncCall(Expr\FuncCall $node) { protected function pExpr_FuncCall(Expr\FuncCall $node) {
return $this->pCallLhs($node->name) return $this->pCallLhs($node->name)
. '(' . $this->pCommaSeparated($node->args) . ')'; . '(' . $this->pMaybeMultiline($node->args) . ')';
} }
protected function pExpr_MethodCall(Expr\MethodCall $node) { protected function pExpr_MethodCall(Expr\MethodCall $node) {
return $this->pDereferenceLhs($node->var) . '->' . $this->pObjectProperty($node->name) return $this->pDereferenceLhs($node->var) . '->' . $this->pObjectProperty($node->name)
. '(' . $this->pCommaSeparated($node->args) . ')'; . '(' . $this->pMaybeMultiline($node->args) . ')';
} }
protected function pExpr_StaticCall(Expr\StaticCall $node) { protected function pExpr_StaticCall(Expr\StaticCall $node) {
@ -453,7 +458,7 @@ class Standard extends PrettyPrinterAbstract
? $this->p($node->name) ? $this->p($node->name)
: '{' . $this->p($node->name) . '}') : '{' . $this->p($node->name) . '}')
: $node->name) : $node->name)
. '(' . $this->pCommaSeparated($node->args) . ')'; . '(' . $this->pMaybeMultiline($node->args) . ')';
} }
protected function pExpr_Empty(Expr\Empty_ $node) { protected function pExpr_Empty(Expr\Empty_ $node) {
@ -501,9 +506,9 @@ class Standard extends PrettyPrinterAbstract
$syntax = $node->getAttribute('kind', $syntax = $node->getAttribute('kind',
$this->options['shortArraySyntax'] ? Expr\Array_::KIND_SHORT : Expr\Array_::KIND_LONG); $this->options['shortArraySyntax'] ? Expr\Array_::KIND_SHORT : Expr\Array_::KIND_LONG);
if ($syntax === Expr\Array_::KIND_SHORT) { if ($syntax === Expr\Array_::KIND_SHORT) {
return '[' . $this->pCommaSeparated($node->items) . ']'; return '[' . $this->pMaybeMultiline($node->items, true) . ']';
} else { } else {
return 'array(' . $this->pCommaSeparated($node->items) . ')'; return 'array(' . $this->pMaybeMultiline($node->items, true) . ')';
} }
} }
@ -553,10 +558,10 @@ class Standard extends PrettyPrinterAbstract
protected function pExpr_New(Expr\New_ $node) { protected function pExpr_New(Expr\New_ $node) {
if ($node->class instanceof Stmt\Class_) { if ($node->class instanceof Stmt\Class_) {
$args = $node->args ? '(' . $this->pCommaSeparated($node->args) . ')' : ''; $args = $node->args ? '(' . $this->pMaybeMultiline($node->args) . ')' : '';
return 'new ' . $this->pClassCommon($node->class, $args); return 'new ' . $this->pClassCommon($node->class, $args);
} }
return 'new ' . $this->p($node->class) . '(' . $this->pCommaSeparated($node->args) . ')'; return 'new ' . $this->p($node->class) . '(' . $this->pMaybeMultiline($node->args) . ')';
} }
protected function pExpr_Clone(Expr\Clone_ $node) { protected function pExpr_Clone(Expr\Clone_ $node) {
@ -944,4 +949,21 @@ class Standard extends PrettyPrinterAbstract
return '(' . $this->p($node) . ')'; return '(' . $this->p($node) . ')';
} }
} }
private function hasNodeWithComments(array $nodes) {
foreach ($nodes as $node) {
if ($node && $node->getAttribute('comments')) {
return true;
}
}
return false;
}
private function pMaybeMultiline(array $nodes, $trailingComma = false) {
if (!$this->hasNodeWithComments($nodes)) {
return $this->pCommaSeparated($nodes);
} else {
return $this->pCommaSeparatedMultiline($nodes, $trailingComma) . "\n";
}
}
} }

View File

@ -286,6 +286,38 @@ abstract class PrettyPrinterAbstract
return $this->pImplode($nodes, ', '); return $this->pImplode($nodes, ', ');
} }
/**
* Pretty prints a comma-separated list of nodes in multiline style, including comments.
*
* The result includes a leading newline and one level of indentation (same as pStmts).
*
* @param Node[] $nodes Array of Nodes to be printed
* @param bool $trailingComma Whether to use a trailing comma
*
* @return string Comma separated pretty printed nodes in multiline style
*/
protected function pCommaSeparatedMultiline(array $nodes, $trailingComma) {
$result = '';
$lastIdx = count($nodes) - 1;
foreach ($nodes as $idx => $node) {
if ($node !== null) {
$comments = $node->getAttribute('comments', array());
if ($comments) {
$result .= "\n" . $this->pComments($comments);
}
$result .= "\n" . $this->p($node);
} else {
$result .= "\n";
}
if ($trailingComma || $idx !== $lastIdx) {
$result .= ',';
}
}
return preg_replace('~\n(?!$|' . $this->noIndentToken . ')~', "\n ", $result);
}
/** /**
* Signals the pretty printer that a string shall not be indented. * Signals the pretty printer that a string shall not be indented.
* *

View File

@ -2,6 +2,7 @@
namespace PhpParser\Builder; namespace PhpParser\Builder;
use PhpParser\Comment\Doc;
use PhpParser\Node; use PhpParser\Node;
use PhpParser\Node\Stmt; use PhpParser\Node\Stmt;
@ -15,19 +16,23 @@ class NamespaceTest extends \PHPUnit_Framework_TestCase
$stmt1 = new Stmt\Class_('SomeClass'); $stmt1 = new Stmt\Class_('SomeClass');
$stmt2 = new Stmt\Interface_('SomeInterface'); $stmt2 = new Stmt\Interface_('SomeInterface');
$stmt3 = new Stmt\Function_('someFunction'); $stmt3 = new Stmt\Function_('someFunction');
$docComment = new Doc('/** Test */');
$expected = new Stmt\Namespace_( $expected = new Stmt\Namespace_(
new Node\Name('Name\Space'), new Node\Name('Name\Space'),
array($stmt1, $stmt2, $stmt3) array($stmt1, $stmt2, $stmt3),
array('comments' => array($docComment))
); );
$node = $this->createNamespaceBuilder('Name\Space') $node = $this->createNamespaceBuilder('Name\Space')
->addStmt($stmt1) ->addStmt($stmt1)
->addStmts(array($stmt2, $stmt3)) ->addStmts(array($stmt2, $stmt3))
->setDocComment($docComment)
->getNode() ->getNode()
; ;
$this->assertEquals($expected, $node); $this->assertEquals($expected, $node);
$node = $this->createNamespaceBuilder(new Node\Name(array('Name', 'Space'))) $node = $this->createNamespaceBuilder(new Node\Name(array('Name', 'Space')))
->setDocComment($docComment)
->addStmts(array($stmt1, $stmt2)) ->addStmts(array($stmt1, $stmt2))
->addStmt($stmt3) ->addStmt($stmt3)
->getNode() ->getNode()

View File

@ -112,6 +112,7 @@ class ParamTest extends \PHPUnit_Framework_TestCase
array('float', 'float'), array('float', 'float'),
array('string', 'string'), array('string', 'string'),
array('iterable', 'iterable'), array('iterable', 'iterable'),
array('object', 'object'),
array('Array', 'array'), array('Array', 'array'),
array('CALLABLE', 'callable'), array('CALLABLE', 'callable'),
array('Some\Class', new Node\Name('Some\Class')), array('Some\Class', new Node\Name('Some\Class')),
@ -155,4 +156,16 @@ class ParamTest extends \PHPUnit_Framework_TestCase
$node $node
); );
} }
public function testVariadic() {
$node = $this->createParamBuilder('test')
->makeVariadic()
->getNode()
;
$this->assertEquals(
new Node\Param('test', null, null, false, true),
$node
);
}
} }

View File

@ -3,6 +3,7 @@
namespace PhpParser\Builder; namespace PhpParser\Builder;
use PhpParser\Comment; use PhpParser\Comment;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt; use PhpParser\Node\Stmt;
class TraitTest extends \PHPUnit_Framework_TestCase class TraitTest extends \PHPUnit_Framework_TestCase
@ -18,19 +19,21 @@ class TraitTest extends \PHPUnit_Framework_TestCase
$prop = new Stmt\Property(Stmt\Class_::MODIFIER_PUBLIC, array( $prop = new Stmt\Property(Stmt\Class_::MODIFIER_PUBLIC, array(
new Stmt\PropertyProperty('test') new Stmt\PropertyProperty('test')
)); ));
$use = new Stmt\TraitUse([new Name('OtherTrait')]);
$trait = $this->createTraitBuilder('TestTrait') $trait = $this->createTraitBuilder('TestTrait')
->setDocComment('/** Nice trait */') ->setDocComment('/** Nice trait */')
->addStmt($method1) ->addStmt($method1)
->addStmts(array($method2, $method3)) ->addStmts([$method2, $method3])
->addStmt($prop) ->addStmt($prop)
->addStmt($use)
->getNode(); ->getNode();
$this->assertEquals(new Stmt\Trait_('TestTrait', array( $this->assertEquals(new Stmt\Trait_('TestTrait', [
'stmts' => array($prop, $method1, $method2, $method3) 'stmts' => [$use, $prop, $method1, $method2, $method3]
), array( ], [
'comments' => array( 'comments' => [
new Comment\Doc('/** Nice trait */') new Comment\Doc('/** Nice trait */')
) ]
)), $trait); ]), $trait);
} }
/** /**

View File

@ -201,7 +201,13 @@ class LexerTest extends \PHPUnit_Framework_TestCase
array(), array() array(), array()
) )
) )
) ),
// tests no tokens
array(
'',
array(),
array()
),
); );
} }

View File

@ -178,7 +178,7 @@ class NodeTraverserTest extends \PHPUnit_Framework_TestCase
$visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); $visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
$visitor->expects($this->at(1))->method('enterNode')->with($mulNode) $visitor->expects($this->at(1))->method('enterNode')->with($mulNode)
->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL)); ->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
$visitor->expects($this->at(2))->method('afterTraversal'); $visitor->expects($this->at(2))->method('afterTraverse');
$traverser = new NodeTraverser; $traverser = new NodeTraverser;
$traverser->addVisitor($visitor); $traverser->addVisitor($visitor);
$this->assertEquals($stmts, $traverser->traverse($stmts)); $this->assertEquals($stmts, $traverser->traverse($stmts));
@ -187,7 +187,7 @@ class NodeTraverserTest extends \PHPUnit_Framework_TestCase
$visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); $visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
$visitor->expects($this->at(2))->method('enterNode')->with($varNode1) $visitor->expects($this->at(2))->method('enterNode')->with($varNode1)
->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL)); ->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
$visitor->expects($this->at(3))->method('afterTraversal'); $visitor->expects($this->at(3))->method('afterTraverse');
$traverser = new NodeTraverser; $traverser = new NodeTraverser;
$traverser->addVisitor($visitor); $traverser->addVisitor($visitor);
$this->assertEquals($stmts, $traverser->traverse($stmts)); $this->assertEquals($stmts, $traverser->traverse($stmts));
@ -196,7 +196,7 @@ class NodeTraverserTest extends \PHPUnit_Framework_TestCase
$visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); $visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
$visitor->expects($this->at(3))->method('leaveNode')->with($varNode1) $visitor->expects($this->at(3))->method('leaveNode')->with($varNode1)
->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL)); ->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
$visitor->expects($this->at(4))->method('afterTraversal'); $visitor->expects($this->at(4))->method('afterTraverse');
$traverser = new NodeTraverser; $traverser = new NodeTraverser;
$traverser->addVisitor($visitor); $traverser->addVisitor($visitor);
$this->assertEquals($stmts, $traverser->traverse($stmts)); $this->assertEquals($stmts, $traverser->traverse($stmts));
@ -205,7 +205,7 @@ class NodeTraverserTest extends \PHPUnit_Framework_TestCase
$visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); $visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock();
$visitor->expects($this->at(6))->method('leaveNode')->with($mulNode) $visitor->expects($this->at(6))->method('leaveNode')->with($mulNode)
->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL)); ->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
$visitor->expects($this->at(7))->method('afterTraversal'); $visitor->expects($this->at(7))->method('afterTraverse');
$traverser = new NodeTraverser; $traverser = new NodeTraverser;
$traverser->addVisitor($visitor); $traverser->addVisitor($visitor);
$this->assertEquals($stmts, $traverser->traverse($stmts)); $this->assertEquals($stmts, $traverser->traverse($stmts));
@ -216,7 +216,7 @@ class NodeTraverserTest extends \PHPUnit_Framework_TestCase
->will($this->returnValue(NodeTraverser::REMOVE_NODE)); ->will($this->returnValue(NodeTraverser::REMOVE_NODE));
$visitor->expects($this->at(7))->method('enterNode')->with($printNode) $visitor->expects($this->at(7))->method('enterNode')->with($printNode)
->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL)); ->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
$visitor->expects($this->at(8))->method('afterTraversal'); $visitor->expects($this->at(8))->method('afterTraverse');
$traverser = new NodeTraverser; $traverser = new NodeTraverser;
$traverser->addVisitor($visitor); $traverser->addVisitor($visitor);
$this->assertEquals([$printNode], $traverser->traverse($stmts)); $this->assertEquals([$printNode], $traverser->traverse($stmts));

View File

@ -169,6 +169,9 @@ EOC;
array("exit(1)", ['kind' => Expr\Exit_::KIND_EXIT]), array("exit(1)", ['kind' => Expr\Exit_::KIND_EXIT]),
array("?>Foo", ['hasLeadingNewline' => false]), array("?>Foo", ['hasLeadingNewline' => false]),
array("?>\nFoo", ['hasLeadingNewline' => true]), array("?>\nFoo", ['hasLeadingNewline' => true]),
array("namespace Foo;", ['kind' => Node\Stmt\Namespace_::KIND_SEMICOLON]),
array("namespace Foo {}", ['kind' => Node\Stmt\Namespace_::KIND_BRACED]),
array("namespace {}", ['kind' => Node\Stmt\Namespace_::KIND_BRACED]),
); );
} }
} }

View File

@ -10,6 +10,9 @@ Comments on blocks
$a; $a;
} }
} }
// empty
{}
----- -----
array( array(
0: Expr_Variable( 0: Expr_Variable(
@ -20,4 +23,9 @@ array(
2: // baz 2: // baz
) )
) )
1: Stmt_Nop(
comments: array(
0: // empty
)
)
) )

View File

@ -248,11 +248,11 @@ $foo->
!!positions !!positions
Syntax error, unexpected ';', expecting T_STRING or T_VARIABLE or '{' or '$' from 3:1 to 3:1 Syntax error, unexpected ';', expecting T_STRING or T_VARIABLE or '{' or '$' from 3:1 to 3:1
array( array(
0: Expr_PropertyFetch[2:1 - 3:1]( 0: Expr_PropertyFetch[2:1 - 2:6](
var: Expr_Variable[2:1 - 2:4]( var: Expr_Variable[2:1 - 2:4](
name: foo name: foo
) )
name: Expr_Error[3:1 - 3:1]( name: Expr_Error[3:1 - 2:6](
) )
) )
) )
@ -272,11 +272,11 @@ array(
) )
returnType: null returnType: null
stmts: array( stmts: array(
0: Expr_PropertyFetch[3:5 - 4:1]( 0: Expr_PropertyFetch[3:5 - 3:10](
var: Expr_Variable[3:5 - 3:8]( var: Expr_Variable[3:5 - 3:8](
name: bar name: bar
) )
name: Expr_Error[4:1 - 4:1]( name: Expr_Error[4:1 - 3:10](
) )
) )
) )
@ -305,8 +305,8 @@ new
!!php7,positions !!php7,positions
Syntax error, unexpected EOF from 2:4 to 2:4 Syntax error, unexpected EOF from 2:4 to 2:4
array( array(
0: Expr_New[2:1 - 2:4]( 0: Expr_New[2:1 - 2:3](
class: Expr_Error[2:4 - 2:4]( class: Expr_Error[2:4 - 2:3](
) )
args: array( args: array(
) )
@ -544,8 +544,6 @@ for ($a, ; $b, ; $c, );
function ($a, ) use ($b, ) {}; function ($a, ) use ($b, ) {};
----- -----
!!php7 !!php7
A trailing comma is not allowed here from 3:9 to 3:9
A trailing comma is not allowed here from 4:18 to 4:18
A trailing comma is not allowed here from 5:6 to 5:6 A trailing comma is not allowed here from 5:6 to 5:6
A trailing comma is not allowed here from 6:13 to 6:13 A trailing comma is not allowed here from 6:13 to 6:13
A trailing comma is not allowed here from 8:21 to 8:21 A trailing comma is not allowed here from 8:21 to 8:21
@ -835,3 +833,34 @@ array(
) )
) )
) )
-----
<?php
foo(Bar::);
-----
!!php7,positions
Syntax error, unexpected ')' from 3:10 to 3:10
array(
0: Expr_FuncCall[3:1 - 3:10](
name: Name[3:1 - 3:3](
parts: array(
0: foo
)
)
args: array(
0: Arg[3:5 - 3:9](
value: Expr_ClassConstFetch[3:5 - 3:9](
class: Name[3:5 - 3:7](
parts: array(
0: Bar
)
)
name: Expr_Error[3:10 - 3:9](
)
)
byRef: false
unpack: false
)
)
)
)

View File

@ -1,7 +1,7 @@
Scalar type declarations Scalar type declarations
----- -----
<?php <?php
function test(bool $a, Int $b, FLOAT $c, StRiNg $d, iterable $e) : void {} function test(bool $a, Int $b, FLOAT $c, StRiNg $d, iterable $e, object $f) : void {}
----- -----
!!php7 !!php7
array( array(
@ -44,6 +44,13 @@ array(
name: e name: e
default: null default: null
) )
5: Param(
type: object
byRef: false
variadic: false
name: f
default: null
)
) )
returnType: void returnType: void
stmts: array( stmts: array(

View File

@ -0,0 +1,22 @@
Trailing comment after braced namespace declaration
-----
<?php
namespace Foo {}
// Comment
-----
array(
0: Stmt_Namespace(
name: Name(
parts: array(
0: Foo
)
)
stmts: array(
)
)
1: Stmt_Nop(
comments: array(
0: // Comment
)
)
)

View File

@ -0,0 +1,47 @@
Group use can have trailing comma
-----
<?php
use A\{B,};
use function A\{b,};
-----
!!php7
array(
0: Stmt_GroupUse(
type: TYPE_UNKNOWN (0)
prefix: Name(
parts: array(
0: A
)
)
uses: array(
0: Stmt_UseUse(
type: TYPE_NORMAL (1)
name: Name(
parts: array(
0: B
)
)
alias: B
)
)
)
1: Stmt_GroupUse(
type: TYPE_FUNCTION (2)
prefix: Name(
parts: array(
0: A
)
)
uses: array(
0: Stmt_UseUse(
type: TYPE_UNKNOWN (0)
name: Name(
parts: array(
0: b
)
)
alias: b
)
)
)
)

View File

@ -0,0 +1,53 @@
Comments in arrays and function calls
-----
<?php
$arr = [
// Foo
$foo,
// Bar
$bar,
// Discarded
];
[
// Foo
$foo,
,
// Bar
$bar,
] = $arr;
foo(
// Foo
$foo,
// Bar
$bar
);
new Foo(
// Foo
$foo
);
-----
!!php7
$arr = [
// Foo
$foo,
// Bar
$bar,
];
[
// Foo
$foo,
,
// Bar
$bar,
] = $arr;
foo(
// Foo
$foo,
// Bar
$bar
);
new Foo(
// Foo
$foo
);