1
0
mirror of https://github.com/danog/PHP-Parser.git synced 2024-11-30 04:19:30 +01:00

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; 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 interface Node
{ {
/** /**
@ -41,6 +55,15 @@ interface Node
*/ */
public function getDocComment(); 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. * Sets an attribute on a node.
* *
@ -74,4 +97,37 @@ interface Node
* @return array * @return array
*/ */
public function getAttributes(); 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; use PhpParser\NodeAbstract;
/**
* @property Name $namespacedName Namespace-prefixed name (requires NameResolver). This only applies
* to freestanding (non-class) constants.
*/
class Const_ extends NodeAbstract class Const_ extends NodeAbstract
{ {
/** @var string Name */ /** @var string Name */

View File

@ -4,6 +4,9 @@ namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr; use PhpParser\Node\Expr;
/**
* @property int $kind One of the KIND_* class constants
*/
class Array_ extends Expr class Array_ extends Expr
{ {
// For use in "kind" attribute // For use in "kind" attribute

View File

@ -4,6 +4,9 @@ namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr; use PhpParser\Node\Expr;
/**
* @property int $kind One of the KIND_* class constants
*/
class Exit_ extends Expr class Exit_ extends Expr
{ {
/* For use in "kind" attribute */ /* For use in "kind" attribute */

View File

@ -4,6 +4,10 @@ namespace PhpParser\Node\Scalar;
use 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 class Encapsed extends Scalar
{ {
/** @var array Encaps list */ /** @var array Encaps list */

View File

@ -5,6 +5,9 @@ namespace PhpParser\Node\Scalar;
use PhpParser\Error; use PhpParser\Error;
use PhpParser\Node\Scalar; use PhpParser\Node\Scalar;
/**
* @property int $kind One of the KIND_* class constants
*/
class LNumber extends Scalar class LNumber extends Scalar
{ {
/* For use in "kind" attribute */ /* For use in "kind" attribute */

View File

@ -5,6 +5,10 @@ namespace PhpParser\Node\Scalar;
use PhpParser\Error; use PhpParser\Error;
use PhpParser\Node\Scalar; 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 class String_ extends Scalar
{ {
/* For use in "kind" attribute */ /* For use in "kind" attribute */

View File

@ -4,6 +4,9 @@ namespace PhpParser\Node\Stmt;
use PhpParser\Node; use PhpParser\Node;
/**
* @property Node\Name $namespacedName Namespace-prefixed name (requires NameResolver)
*/
abstract class ClassLike extends Node\Stmt { abstract class ClassLike extends Node\Stmt {
/** @var string Name */ /** @var string Name */
public $name; public $name;

View File

@ -5,6 +5,9 @@ namespace PhpParser\Node\Stmt;
use PhpParser\Node; use PhpParser\Node;
use PhpParser\Node\FunctionLike; use PhpParser\Node\FunctionLike;
/**
* @property Node\Name $namespacedName Namespace-prefixed name (requires NameResolver)
*/
class Function_ extends Node\Stmt implements FunctionLike class Function_ extends Node\Stmt implements FunctionLike
{ {
/** @var bool Whether function returns by reference */ /** @var bool Whether function returns by reference */

View File

@ -4,6 +4,9 @@ namespace PhpParser\Node\Stmt;
use 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 class InlineHTML extends Stmt
{ {
/** @var string String */ /** @var string String */

View File

@ -4,6 +4,7 @@ namespace PhpParser;
abstract class NodeAbstract implements Node, \JsonSerializable abstract class NodeAbstract implements Node, \JsonSerializable
{ {
/** @var array Node attributes */
protected $attributes; protected $attributes;
/** /**
@ -108,4 +109,24 @@ abstract class NodeAbstract implements Node, \JsonSerializable
public function jsonSerialize() { public function jsonSerialize() {
return ['nodeType' => $this->getType()] + get_object_vars($this); 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 // unqualified names inside a namespace cannot be resolved at compile-time
// add the namespaced version of the name as an attribute // add the namespaced version of the name as an attribute
$name->setAttribute('namespacedName', $name->namespacedName =
FullyQualified::concat($this->namespace, $name, $name->getAttributes())); FullyQualified::concat($this->namespace, $name, $name->getAttributes());
return $name; return $name;
} }
@ -254,4 +254,4 @@ class NameResolver extends NodeVisitorAbstract
$node->namespacedName = new Name($node->name); $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 = new DummyNode('value1', 'value2', $attributes);
$node->notSubNode = 'value3'; $attributes['notSubNode'] = $node->notSubNode = 'value3';
return array( return array(
array($attributes, $node), array($attributes, $node),
@ -119,7 +119,7 @@ class NodeAbstractTest extends \PHPUnit_Framework_TestCase
*/ */
public function testIteration(array $attributes, Node $node) { public function testIteration(array $attributes, Node $node) {
// Iteration is simple object iteration over properties, // Iteration is simple object iteration over properties,
// not over subnodes // which coincide with the subnodes
$i = 0; $i = 0;
foreach ($node as $key => $value) { foreach ($node as $key => $value) {
if ($i === 0) { if ($i === 0) {
@ -128,15 +128,12 @@ class NodeAbstractTest extends \PHPUnit_Framework_TestCase
} else if ($i === 1) { } else if ($i === 1) {
$this->assertSame('subNode2', $key); $this->assertSame('subNode2', $key);
$this->assertSame('value2', $value); $this->assertSame('value2', $value);
} else if ($i === 2) {
$this->assertSame('notSubNode', $key);
$this->assertSame('value3', $value);
} else { } else {
throw new \Exception; throw new \Exception;
} }
$i++; $i++;
} }
$this->assertSame(3, $i); $this->assertSame(2, $i);
} }
public function testAttributes() { public function testAttributes() {
@ -165,6 +162,28 @@ class NodeAbstractTest extends \PHPUnit_Framework_TestCase
), ),
$node->getAttributes() $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() { public function testJsonSerialization() {