* * For the full copyright and license information refer to the LICENSE file * distributed with this source code */ namespace Peast\Syntax\Node; use Peast\Syntax\SourceLocation; use Peast\Syntax\Position; /** * Base class for all the nodes generated by Peast. * * @author Marco MarchiĆ² * * @abstract */ abstract class Node implements \JSONSerializable { /** * Map of node properties * * @var array */ protected $propertiesMap = array( "type" => false, "location" => false, "leadingComments" => false, "trailingComments" => false ); /** * Node location in the source code * * @var SourceLocation */ public $location; /** * Leading comments array * * @var Comment[] */ protected $leadingComments = array(); /** * Trailing comments array * * @var Comment[] */ protected $trailingComments = array(); /** * Class constructor */ public function __construct() { $this->location = new SourceLocation; } /** * Returns node type * * @return string */ public function getType() { $class = explode("\\", get_class($this)); return array_pop($class); } /** * Sets leading comments array * * @param Comment[] $comments Comments array * * @return $this */ public function setLeadingComments($comments) { $this->assertArrayOf($comments, "Comment"); $this->leadingComments = $comments; return $this; } /** * Returns leading comments array * * @return Comment[] */ public function getLeadingComments() { return $this->leadingComments; } /** * Sets trailing comments array * * @param Comment[] $comments Comments array * * @return $this */ public function setTrailingComments($comments) { $this->assertArrayOf($comments, "Comment"); $this->trailingComments = $comments; return $this; } /** * Returns trailing comments array * * @return Comment[] */ public function getTrailingComments() { return $this->trailingComments; } /** * Returns node location in the source code * * @return SourceLocation */ public function getLocation() { return $this->location; } /** * Sets the start position of the node in the source code * * @param Position $position Start position * * @return $this */ public function setStartPosition(Position $position) { $this->location->start = $position; return $this; } /** * Sets the end position of the node in the source code * * @param Position $position Start position * * @return $this */ public function setEndPosition(Position $position) { $this->location->end = $position; return $this; } /** * Traverses the current node and all its child nodes using the given * function * * @param callable $fn Function that will be called on each node * @param array $options Options array. See Traverser class * documentation for available options * * @return $this */ public function traverse(callable $fn, $options = array()) { $traverser = new \Peast\Traverser($options); $traverser->addFunction($fn)->traverse($this); return $this; } /** * Returns a serializable version of the node * * @return array */ #[\ReturnTypeWillChange] public function jsonSerialize() { $ret = array(); $props = \Peast\Syntax\Utils::getNodeProperties($this); foreach ($props as $prop) { $ret[$prop["name"]] = $this->{$prop["getter"]}(); } return $ret; } /** * Renders the current node * * @param \Peast\Formatter\Base $formatter Formatter to use for the * rendering * * @return string */ public function render(\Peast\Formatter\Base $formatter) { $renderer = new \Peast\Renderer(); return $renderer->setFormatter($formatter)->render($this); } /** * Asserts that the given value is an array of defined type * * @param mixed $params Value to check * @param string|array $classes Class or array of classes to check against * @param bool $allowNull If true, null values are allowed * * @return void * * @codeCoverageIgnore */ protected function assertArrayOf($params, $classes, $allowNull = false) { if (!is_array($classes)) { $classes = array($classes); } if (!is_array($params)) { $this->typeError($params, $classes, $allowNull, true); } else { foreach ($params as $param) { foreach ($classes as $class) { if ($param === null && $allowNull) { continue 2; } else { $c = "Peast\\Syntax\\Node\\$class"; if ($param instanceof $c) { continue 2; } } } $this->typeError($param, $classes, $allowNull, true, true); } } } /** * Asserts that the given value respects the defined type * * @param mixed $param Value to check * @param string|array $classes Class or array of classes to check against * @param bool $allowNull If true, null values are allowed * * @return void * * @codeCoverageIgnore */ protected function assertType($param, $classes, $allowNull = false) { if (!is_array($classes)) { $classes = array($classes); } if ($param === null) { if (!$allowNull) { $this->typeError($param, $classes, $allowNull); } } else { foreach ($classes as $class) { $c = "Peast\\Syntax\\Node\\$class"; if ($param instanceof $c) { return; } } $this->typeError($param, $classes, $allowNull); } } /** * Throws an error if the defined type is not supported b * * @param mixed $param The value to check * @param mixed $allowedTypes Class or array of classes to check against * @param bool $allowNull If true, null values are allowed * @param bool $array If true, the value must be an array * @param bool $inArray If true, the value is an array but the content * does not respects the type * * @return void * * @throws \TypeError * * @codeCoverageIgnore */ protected function typeError( $param, $allowedTypes, $allowNull = false, $array = false, $inArray = false ) { $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); $method = $backtrace[2]["class"] . "::" . $backtrace[2]["function"]; $msg = "Argument 0 passed to $method must be "; if ($array) { $msg .= "an array of "; } $msg .= implode(" or ", $allowedTypes); if ($allowNull) { $msg .= " or null"; } if (is_object($param)) { $parts = explode("\\", get_class($param)); $type = array_pop($parts); } else { $type = gettype($param); } if ($inArray) { $type = "array of $type"; } $msg .= ", $type given"; if (version_compare(phpversion(), '7', '>=')) { throw new \TypeError($msg); } else { trigger_error($msg, E_USER_ERROR); } } }