1
0
mirror of https://github.com/nikic/PHP-Parser.git synced 2025-07-22 14:51:29 +02:00

Compare commits

...

11 Commits

Author SHA1 Message Date
Nikita Popov
579f4ce846 Release PHP-Parser 3.1.3 2017-12-26 15:43:21 +01:00
Nikita Popov
94ca9a7ab9 Use Tokens::class in lexer
Ref .
2017-11-13 01:14:55 +01:00
Maks Rafalko
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
Nikita Popov
08131e7ff2 Release PHP-Parser 3.1.2 2017-11-04 12:48:34 +01:00
Nikita Popov
0ba710affa Add setDocComment() to namespace build () 2017-11-04 12:43:02 +01:00
Alexander Miertsch
72231abe6d ClassMethod stmts property can be null ()
Conflicts:
	lib/PhpParser/Node/Stmt/ClassMethod.php
2017-11-04 12:38:45 +01:00
Nikita Popov
d418bf3951 Preserve comments on empty blocks () 2017-10-01 16:54:43 +02:00
Nikita Popov
5a9fbca54a Add attribute for namespace kinds ()
One of KIND_SEMICOLON or KIND_BRACED.
2017-09-26 18:45:05 +02:00
Nikita Popov
a1e8e1a30e Release PHP-Parser 3.1.1 2017-09-02 19:10:46 +02:00
Nikita Popov
d77e6cd6e9 Allow TraitUse statements in trait builder
Fixes .
2017-08-29 23:18:59 +02:00
Nikita Popov
a10780ca0d Handle Nop statement after brace-style namespace
Fixes .
2017-08-29 23:14:27 +02:00
18 changed files with 172 additions and 47 deletions

@@ -1,8 +1,37 @@
Version 3.1.1-dev
Version 3.1.4-dev
-----------------
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)
--------------------------

@@ -56,11 +56,17 @@ top_statement:
| T_HALT_COMPILER
{ $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; }
| 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 '}'
{ $$ = Stmt\Namespace_[$2, $4]; $this->checkNamespace($$); }
{ $$ = Stmt\Namespace_[$2, $4];
$$->setAttribute('kind', Stmt\Namespace_::KIND_BRACED);
$this->checkNamespace($$); }
| 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_type use_declarations ';' { $$ = Stmt\Use_[$3, $2]; }
| group_use_declaration ';' { $$ = $1; }
@@ -155,7 +161,15 @@ inner_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
{ $$ = 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 ';'

@@ -70,11 +70,17 @@ top_statement:
| T_HALT_COMPILER
{ $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; }
| 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 '}'
{ $$ = Stmt\Namespace_[$2, $4]; $this->checkNamespace($$); }
{ $$ = Stmt\Namespace_[$2, $4];
$$->setAttribute('kind', Stmt\Namespace_::KIND_BRACED);
$this->checkNamespace($$); }
| 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_type use_declarations semi { $$ = Stmt\Use_[$3, $2]; }
| group_use_declaration semi { $$ = $1; }
@@ -191,7 +197,15 @@ inner_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
{ $$ = Stmt\If_[$3, ['stmts' => toArray($5), 'elseifs' => $6, 'else' => $7]]; }
| T_IF '(' expr ')' ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';'

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

@@ -6,7 +6,7 @@ use PhpParser;
use PhpParser\Node;
use PhpParser\Node\Stmt;
class Namespace_ extends PhpParser\BuilderAbstract
class Namespace_ extends Declaration
{
private $name;
private $stmts = array();
@@ -33,27 +33,12 @@ class Namespace_ extends PhpParser\BuilderAbstract
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.
*
* @return Node The built node
*/
public function getNode() {
return new Stmt\Namespace_($this->name, $this->stmts);
return new Stmt\Namespace_($this->name, $this->stmts, $this->attributes);
}
}

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

@@ -360,7 +360,7 @@ class Lexer
if ('T_HASHBANG' === $name) {
// HHVM uses a special token for #! hashbang lines
$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
$tokenMap[$i] = constant($name);
}

@@ -17,7 +17,7 @@ class ClassMethod extends Node\Stmt implements FunctionLike
public $params;
/** @var null|string|Node\Name|Node\NullableType Return type */
public $returnType;
/** @var Node[] Statements */
/** @var Node[]|null Statements */
public $stmts;
/** @deprecated Use $flags instead */

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

@@ -21,7 +21,15 @@ abstract class NodeAbstract implements Node, \JsonSerializable
* @return string Type of the node
*/
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
),
'\\',
'_'
);
}
/**

@@ -1257,15 +1257,21 @@ class Php5 extends \PhpParser\ParserAbstract
}
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() {
$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() {
$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() {
@@ -1410,7 +1416,14 @@ class Php5 extends \PhpParser\ParserAbstract
}
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() {

@@ -1191,15 +1191,21 @@ class Php7 extends \PhpParser\ParserAbstract
}
protected function reduceRule95() {
$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 reduceRule96() {
$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 reduceRule97() {
$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 reduceRule98() {
@@ -1364,7 +1370,14 @@ class Php7 extends \PhpParser\ParserAbstract
}
protected function reduceRule138() {
$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 reduceRule139() {

@@ -445,6 +445,7 @@ abstract class ParserAbstract implements Parser
if ($stmt instanceof Node\Stmt\Namespace_) {
$afterFirstNamespace = true;
} elseif (!$stmt instanceof Node\Stmt\HaltCompiler
&& !$stmt instanceof Node\Stmt\Nop
&& $afterFirstNamespace && !$hasErrored) {
$this->emitError(new Error(
'No code may exist outside of namespace {}', $stmt->getAttributes()));

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

@@ -3,6 +3,7 @@
namespace PhpParser\Builder;
use PhpParser\Comment;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
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(
new Stmt\PropertyProperty('test')
));
$use = new Stmt\TraitUse([new Name('OtherTrait')]);
$trait = $this->createTraitBuilder('TestTrait')
->setDocComment('/** Nice trait */')
->addStmt($method1)
->addStmts(array($method2, $method3))
->addStmts([$method2, $method3])
->addStmt($prop)
->addStmt($use)
->getNode();
$this->assertEquals(new Stmt\Trait_('TestTrait', array(
'stmts' => array($prop, $method1, $method2, $method3)
), array(
'comments' => array(
$this->assertEquals(new Stmt\Trait_('TestTrait', [
'stmts' => [$use, $prop, $method1, $method2, $method3]
], [
'comments' => [
new Comment\Doc('/** Nice trait */')
)
)), $trait);
]
]), $trait);
}
/**

@@ -169,6 +169,9 @@ EOC;
array("exit(1)", ['kind' => Expr\Exit_::KIND_EXIT]),
array("?>Foo", ['hasLeadingNewline' => false]),
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]),
);
}
}

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

@@ -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
)
)
)