From 91f77968c275e13a956dded1ec864f52de72db14 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 19 Oct 2016 23:36:28 +0200 Subject: [PATCH] Add Node property overloads for accessing attributes Node attributes are now also accessible as ordinary properties. Accessing an undefined attribute throws -- the getAttribute() default value behavior can be replicated using coalescing. @property declarations for all the standard attributes have been added to the relevant nodes. Of course, whether they are actually available depends on configuration. --- lib/PhpParser/Node.php | 56 ++++++++++++++++++++++ lib/PhpParser/Node/Const_.php | 4 ++ lib/PhpParser/Node/Expr/Array_.php | 3 ++ lib/PhpParser/Node/Expr/Exit_.php | 3 ++ lib/PhpParser/Node/Scalar/Encapsed.php | 4 ++ lib/PhpParser/Node/Scalar/LNumber.php | 3 ++ lib/PhpParser/Node/Scalar/String_.php | 4 ++ lib/PhpParser/Node/Stmt/ClassLike.php | 3 ++ lib/PhpParser/Node/Stmt/Function_.php | 3 ++ lib/PhpParser/Node/Stmt/InlineHTML.php | 3 ++ lib/PhpParser/NodeAbstract.php | 21 ++++++++ lib/PhpParser/NodeVisitor/NameResolver.php | 6 +-- test/PhpParser/NodeAbstractTest.php | 31 +++++++++--- 13 files changed, 135 insertions(+), 9 deletions(-) diff --git a/lib/PhpParser/Node.php b/lib/PhpParser/Node.php index a28ccc1..fc48b2a 100644 --- a/lib/PhpParser/Node.php +++ b/lib/PhpParser/Node.php @@ -2,6 +2,20 @@ namespace PhpParser; +/** + * AST node. + * + * The following attributes are available depending on the 'usedAttributes' option passed to the + * lexer. + * + * @property Comment[] $comments Comments preceding this node + * @property int $startLine Line the node starts at (1-based) + * @property int $endLine Line the node end at (1-based) + * @property int $startFilePos File offset the node starts at (0-based) + * @property int $endFilePos File offset the node ends at (0-based) + * @property int $startTokenPos Token offset the node starts at (0-based) + * @property int $endTokenPos Token offset the node ends at (0-based) + */ interface Node { /** @@ -41,6 +55,15 @@ interface Node */ public function getDocComment(); + /** + * Sets the doc comment of the node. + * + * This will either replace an existing doc comment or add it to the comments array. + * + * @param Comment\Doc $docComment Doc comment to set + */ + public function setDocComment(Comment\Doc $docComment); + /** * Sets an attribute on a node. * @@ -74,4 +97,37 @@ interface Node * @return array */ public function getAttributes(); + + /** + * Get the value of an attribute. + * + * @param string $key Name of the attribute + * + * @return mixed Value of the attribute + */ + public function &__get($key); + + /** + * Sets the value of an attribute. + * + * @param string $key Name of the attribute + * @param mixed $value Value to set + */ + public function __set($key, $value); + + /** + * Check whether an attribute exists and is non-null. + * + * @param string $key Name of the attribute + * + * @return bool Whether the attribute exists and is non-null + */ + public function __isset($key); + + /** + * Removes an attribute. + * + * @param string $key Name of the attribute. + */ + public function __unset($key); } \ No newline at end of file diff --git a/lib/PhpParser/Node/Const_.php b/lib/PhpParser/Node/Const_.php index 26322d4..a7b129f 100644 --- a/lib/PhpParser/Node/Const_.php +++ b/lib/PhpParser/Node/Const_.php @@ -4,6 +4,10 @@ namespace PhpParser\Node; use PhpParser\NodeAbstract; +/** + * @property Name $namespacedName Namespace-prefixed name (requires NameResolver). This only applies + * to freestanding (non-class) constants. + */ class Const_ extends NodeAbstract { /** @var string Name */ diff --git a/lib/PhpParser/Node/Expr/Array_.php b/lib/PhpParser/Node/Expr/Array_.php index 9151f27..73ea890 100644 --- a/lib/PhpParser/Node/Expr/Array_.php +++ b/lib/PhpParser/Node/Expr/Array_.php @@ -4,6 +4,9 @@ namespace PhpParser\Node\Expr; use PhpParser\Node\Expr; +/** + * @property int $kind One of the KIND_* class constants + */ class Array_ extends Expr { // For use in "kind" attribute diff --git a/lib/PhpParser/Node/Expr/Exit_.php b/lib/PhpParser/Node/Expr/Exit_.php index b0b94cf..60d55f3 100644 --- a/lib/PhpParser/Node/Expr/Exit_.php +++ b/lib/PhpParser/Node/Expr/Exit_.php @@ -4,6 +4,9 @@ namespace PhpParser\Node\Expr; use PhpParser\Node\Expr; +/** + * @property int $kind One of the KIND_* class constants + */ class Exit_ extends Expr { /* For use in "kind" attribute */ diff --git a/lib/PhpParser/Node/Scalar/Encapsed.php b/lib/PhpParser/Node/Scalar/Encapsed.php index ec3e971..443c6fa 100644 --- a/lib/PhpParser/Node/Scalar/Encapsed.php +++ b/lib/PhpParser/Node/Scalar/Encapsed.php @@ -4,6 +4,10 @@ namespace PhpParser\Node\Scalar; use PhpParser\Node\Scalar; +/** + * @property int $kind One of the String_::KIND_* class constants + * @property string $docLabel Label of doc comment (only available if string defined as doc string) + */ class Encapsed extends Scalar { /** @var array Encaps list */ diff --git a/lib/PhpParser/Node/Scalar/LNumber.php b/lib/PhpParser/Node/Scalar/LNumber.php index 3559b98..876dc74 100644 --- a/lib/PhpParser/Node/Scalar/LNumber.php +++ b/lib/PhpParser/Node/Scalar/LNumber.php @@ -5,6 +5,9 @@ namespace PhpParser\Node\Scalar; use PhpParser\Error; use PhpParser\Node\Scalar; +/** + * @property int $kind One of the KIND_* class constants + */ class LNumber extends Scalar { /* For use in "kind" attribute */ diff --git a/lib/PhpParser/Node/Scalar/String_.php b/lib/PhpParser/Node/Scalar/String_.php index d7bbf83..d9a7454 100644 --- a/lib/PhpParser/Node/Scalar/String_.php +++ b/lib/PhpParser/Node/Scalar/String_.php @@ -5,6 +5,10 @@ namespace PhpParser\Node\Scalar; use PhpParser\Error; use PhpParser\Node\Scalar; +/** + * @property int $kind One of the KIND_* class constants + * @property string $docLabel Label of doc comment (only available if string defined as doc string) + */ class String_ extends Scalar { /* For use in "kind" attribute */ diff --git a/lib/PhpParser/Node/Stmt/ClassLike.php b/lib/PhpParser/Node/Stmt/ClassLike.php index f6831a6..ec090ac 100644 --- a/lib/PhpParser/Node/Stmt/ClassLike.php +++ b/lib/PhpParser/Node/Stmt/ClassLike.php @@ -4,6 +4,9 @@ namespace PhpParser\Node\Stmt; use PhpParser\Node; +/** + * @property Node\Name $namespacedName Namespace-prefixed name (requires NameResolver) + */ abstract class ClassLike extends Node\Stmt { /** @var string Name */ public $name; diff --git a/lib/PhpParser/Node/Stmt/Function_.php b/lib/PhpParser/Node/Stmt/Function_.php index 2df68e5..9a9c24a 100644 --- a/lib/PhpParser/Node/Stmt/Function_.php +++ b/lib/PhpParser/Node/Stmt/Function_.php @@ -5,6 +5,9 @@ namespace PhpParser\Node\Stmt; use PhpParser\Node; use PhpParser\Node\FunctionLike; +/** + * @property Node\Name $namespacedName Namespace-prefixed name (requires NameResolver) + */ class Function_ extends Node\Stmt implements FunctionLike { /** @var bool Whether function returns by reference */ diff --git a/lib/PhpParser/Node/Stmt/InlineHTML.php b/lib/PhpParser/Node/Stmt/InlineHTML.php index accebe6..3c8ffb1 100644 --- a/lib/PhpParser/Node/Stmt/InlineHTML.php +++ b/lib/PhpParser/Node/Stmt/InlineHTML.php @@ -4,6 +4,9 @@ namespace PhpParser\Node\Stmt; use PhpParser\Node\Stmt; +/** + * @property bool $hasLeadingNewline Whether the inline has a leading newline that has been ignored + */ class InlineHTML extends Stmt { /** @var string String */ diff --git a/lib/PhpParser/NodeAbstract.php b/lib/PhpParser/NodeAbstract.php index 62a96da..37230cd 100644 --- a/lib/PhpParser/NodeAbstract.php +++ b/lib/PhpParser/NodeAbstract.php @@ -4,6 +4,7 @@ namespace PhpParser; abstract class NodeAbstract implements Node, \JsonSerializable { + /** @var array Node attributes */ protected $attributes; /** @@ -108,4 +109,24 @@ abstract class NodeAbstract implements Node, \JsonSerializable public function jsonSerialize() { return ['nodeType' => $this->getType()] + get_object_vars($this); } + + public function &__get($key) { + if (!array_key_exists($key, $this->attributes)) { + throw new \LogicException("Attribute \"$key\" does not exist"); + } + + return $this->attributes[$key]; + } + + public function __set($key, $value) { + $this->attributes[$key] = $value; + } + + public function __isset($key) { + return isset($this->attributes[$key]); + } + + public function __unset($key) { + unset($this->attributes[$key]); + } } diff --git a/lib/PhpParser/NodeVisitor/NameResolver.php b/lib/PhpParser/NodeVisitor/NameResolver.php index 02ec1c8..1282b05 100644 --- a/lib/PhpParser/NodeVisitor/NameResolver.php +++ b/lib/PhpParser/NodeVisitor/NameResolver.php @@ -234,8 +234,8 @@ class NameResolver extends NodeVisitorAbstract // unqualified names inside a namespace cannot be resolved at compile-time // add the namespaced version of the name as an attribute - $name->setAttribute('namespacedName', - FullyQualified::concat($this->namespace, $name, $name->getAttributes())); + $name->namespacedName = + FullyQualified::concat($this->namespace, $name, $name->getAttributes()); return $name; } @@ -254,4 +254,4 @@ class NameResolver extends NodeVisitorAbstract $node->namespacedName = new Name($node->name); } } -} +} \ No newline at end of file diff --git a/test/PhpParser/NodeAbstractTest.php b/test/PhpParser/NodeAbstractTest.php index 7fd0a39..7b10df0 100644 --- a/test/PhpParser/NodeAbstractTest.php +++ b/test/PhpParser/NodeAbstractTest.php @@ -34,7 +34,7 @@ class NodeAbstractTest extends \PHPUnit_Framework_TestCase ); $node = new DummyNode('value1', 'value2', $attributes); - $node->notSubNode = 'value3'; + $attributes['notSubNode'] = $node->notSubNode = 'value3'; return array( array($attributes, $node), @@ -119,7 +119,7 @@ class NodeAbstractTest extends \PHPUnit_Framework_TestCase */ public function testIteration(array $attributes, Node $node) { // Iteration is simple object iteration over properties, - // not over subnodes + // which coincide with the subnodes $i = 0; foreach ($node as $key => $value) { if ($i === 0) { @@ -128,15 +128,12 @@ class NodeAbstractTest extends \PHPUnit_Framework_TestCase } else if ($i === 1) { $this->assertSame('subNode2', $key); $this->assertSame('value2', $value); - } else if ($i === 2) { - $this->assertSame('notSubNode', $key); - $this->assertSame('value3', $value); } else { throw new \Exception; } $i++; } - $this->assertSame(3, $i); + $this->assertSame(2, $i); } public function testAttributes() { @@ -165,6 +162,28 @@ class NodeAbstractTest extends \PHPUnit_Framework_TestCase ), $node->getAttributes() ); + + // Test overloaded properties accessing attributes as well + $this->assertTrue(isset($node->key)); + $this->assertSame('value', $node->key); + $node->key = 'newValue'; + $this->assertSame('newValue', $node->key); + unset($node->key); + $this->assertFalse(isset($node->key)); + + $this->assertFalse(isset($node->null)); // False per standard semantics + $this->assertNull($node->null); + $this->assertSame(array('null' => null), $node->getAttributes()); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Attribute "foo" does not exist + */ + public function testUnknownAttribute() { + /** @var $node Node */ + $node = $this->getMockForAbstractClass('PhpParser\NodeAbstract'); + $node->foo; } public function testJsonSerialization() {