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.
This commit is contained in:
Nikita Popov 2016-10-19 23:36:28 +02:00
parent 4e25f51581
commit 91f77968c2
13 changed files with 135 additions and 9 deletions

View File

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

View File

@ -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 */

View File

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

View File

@ -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 */

View File

@ -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 */

View File

@ -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 */

View File

@ -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 */

View File

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

View File

@ -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 */

View File

@ -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 */

View File

@ -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]);
}
}

View File

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

View File

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