xarxaprod-wp-theme/vendor/mck89/peast/lib/Peast/Renderer.php

1195 lines
46 KiB
PHP

<?php
/**
* This file is part of the Peast package
*
* (c) Marco Marchiò <marco.mm89@gmail.com>
*
* For the full copyright and license information refer to the LICENSE file
* distributed with this source code
*/
namespace Peast;
use Peast\Syntax\Node\Comment;
/**
* Nodes renderer class
*
* @author Marco Marchiò <marco.mm89@gmail.com>
*/
class Renderer
{
/**
* Formatter to use for the rendering
*
* @var Formatter\Base
*/
protected $formatter;
/**
* Rendering options taken from the formatter
*
* @var object
*/
protected $renderOpts;
/**
* Node types that does not require semicolon insertion
*
* @var array
*/
protected $noSemicolon = array(
"ClassDeclaration",
"ExportDefaultDeclaration",
"ForInStatement",
"ForOfStatement",
"ForStatement",
"FunctionDeclaration",
"IfStatement",
"LabeledStatement",
"StaticBlock",
"SwitchStatement",
"TryStatement",
"WhileStatement",
"WithStatement",
"MethodDefinition"
);
/**
* Sets the formatter to use for the rendering
*
* @param Formatter\Base $formatter Formatter
*
* @return $this
*/
public function setFormatter(Formatter\Base $formatter)
{
$this->formatter = $formatter;
$this->renderOpts = (object) array(
"nl" => $this->formatter->getNewLine(),
"ind" => $this->formatter->getIndentation(),
"nlbc" => $this->formatter->getNewLineBeforeCurlyBracket(),
"sao" => $this->formatter->getSpacesAroundOperator() ? " " : "",
"sirb" => $this->formatter->getSpacesInsideRoundBrackets() ? " " : "",
"awb" => $this->formatter->getAlwaysWrapBlocks(),
"com" => $this->formatter->getRenderComments(),
"rci" => $this->formatter->getRecalcCommentsIndent()
);
return $this;
}
/**
* Returns the formatter to use for the rendering
*
* @return Formatter\Base
*/
public function getFormatter()
{
return $this->formatter;
}
/**
* Renders the given node
*
* @param Syntax\Node\Node $node Node to render
*
* @return string
*
* @throws \Exception
*/
public function render(Syntax\Node\Node $node)
{
//Throw exception if no formatter has been specified
if (!$this->formatter) {
throw new \Exception("Formatter not set");
}
//Reset indentation level
$this->renderOpts->indLevel = 0;
//Start rendering
return $this->renderNode($node);
}
/**
* Renders a node
*
* @param Syntax\Node\Node $node Node to render
* @param bool $addSemicolon True to add semicolon after node
* rendered code
*
* @return string
*/
protected function renderNode(Syntax\Node\Node $node, $addSemicolon = false)
{
$code = "";
if ($this->renderOpts->com) {
$code .= $this->renderComments($node);
}
$type = $node->getType();
switch ($type) {
case "ArrayExpression":
case "ArrayPattern":
$code .= "[" .
$this->joinNodes(
$node->getElements(),
"," . $this->renderOpts->sao
) .
"]";
break;
case "ArrowFunctionExpression":
if ($node->getAsync()) {
$code .= "async" . $this->renderOpts->sao;
}
$code .= "(" .
$this->renderOpts->sirb .
$this->joinNodes(
$node->getParams(),
"," . $this->renderOpts->sao
) .
$this->renderOpts->sirb .
")" .
$this->renderOpts->sao .
"=>";
$body = $node->getBody();
if ($body->getType() !== "BlockStatement") {
$code .= $this->renderOpts->sao . $this->renderNode($body);
} else {
$code .= $this->renderStatementBlock($body, true);
}
break;
case "AwaitExpression":
$code .= "await " . $this->renderNode($node->getArgument());
break;
case "AssignmentExpression":
case "AssignmentPattern":
case "BinaryExpression":
case "LogicalExpression":
$operator = $type === "AssignmentPattern" ?
"=" :
$node->getOperator();
$code .= $this->renderNode($node->getLeft());
$codeRight = $this->renderNode($node->getRight());
if (preg_match("#^[a-z]+$#i", $operator)) {
$code .= " " .
$operator .
" ";
} else {
//If there's no space around the operator, additional checks must
//be performed to prevent errors when rendering unary and update
//expressions inside binary expressions
$checkSpace = !$this->renderOpts->sao && $type === "BinaryExpression";
//The space is mandatory if the left part ends with the same
//character used as operator
if ($checkSpace && $code && substr($code, -1) === $operator) {
$code .= " ";
}
$code .= $this->renderOpts->sao .
$operator .
$this->renderOpts->sao;
//The space is mandatory if the right part begins with the same
//character used as operator
if ($checkSpace && $codeRight && $codeRight[0] === $operator) {
$code .= " ";
}
}
$code .= $codeRight;
break;
case "BlockStatement":
case "ClassBody":
case "Program":
$code .= $this->renderStatementBlock(
$node->getBody(), false, false, true, false
);
break;
case "BreakStatement":
case "ContinueStatement":
$code .= $type === "BreakStatement" ? "break" : "continue";
if ($label = $node->getLabel()) {
$code .= " " . $this->renderNode($label);
}
break;
case "CallExpression":
case "NewExpression":
if ($type === "NewExpression") {
$code .= "new ";
$optional = false;
} else {
$optional = $node->getOptional();
}
$code .= $this->renderNode($node->getCallee()) .
($optional ? "?." : "") .
"(" .
$this->renderOpts->sirb .
$this->joinNodes(
$node->getArguments(),
"," . $this->renderOpts->sao
) .
$this->renderOpts->sirb .
")";
break;
case "CatchClause":
$code .= "catch";
if ($params = $node->getParam()) {
$code .= $this->renderOpts->sao .
"(" .
$this->renderOpts->sirb .
$this->renderNode($params) .
$this->renderOpts->sirb .
")";
}
$code .= $this->renderStatementBlock($node->getBody(), true);
break;
case "ChainExpression":
case "ExpressionStatement":
$code .= $this->renderNode($node->getExpression());
break;
case "ClassExpression":
case "ClassDeclaration":
$code .= "class";
if ($id = $node->getId()) {
$code .= " " . $this->renderNode($id);
}
if ($superClass = $node->getSuperClass()) {
$code .= " extends " . $this->renderNode($superClass);
}
$code .= $this->renderStatementBlock(
$node->getBody(), true
);
break;
case "ConditionalExpression":
$code .= $this->renderNode($node->getTest()) .
$this->renderOpts->sao .
"?" .
$this->renderOpts->sao .
$this->renderNode($node->getConsequent()) .
$this->renderOpts->sao .
":" .
$this->renderOpts->sao .
$this->renderNode($node->getAlternate());
break;
case "DebuggerStatement":
$code .= "debugger";
break;
case "DoWhileStatement":
$code .= "do" .
$this->renderStatementBlock(
$node->getBody(), null, true
) .
$this->renderOpts->sao .
"while" .
$this->renderOpts->sao .
"(" .
$this->renderOpts->sirb .
$this->renderNode($node->getTest()) .
$this->renderOpts->sirb .
")";
break;
case "JSXEmptyExpression":
case "EmptyStatement":
break;
case "ExportAllDeclaration":
$code .= "export *";
$exported = $node->getExported();
if ($exported) {
$code .= " as " . $this->renderNode($exported);
}
$code .= " from " . $this->renderNode($node->getSource());
break;
case "ExportDefaultDeclaration":
$declaration = $node->getDeclaration();
$code .= "export default " .
$this->renderNode($declaration);
if ($this->requiresSemicolon($declaration)) {
$code .= ";";
}
break;
case "ExportNamedDeclaration":
$code .= "export";
if ($dec = $node->getDeclaration()) {
$code .= " " .
$this->renderNode($dec);
} else {
$code .= $this->renderOpts->sao .
"{" .
$this->joinNodes(
$node->getSpecifiers(),
"," . $this->renderOpts->sao
) .
"}";
if ($source = $node->getSource()) {
$code .= $this->renderOpts->sao .
"from " .
$this->renderNode($source);
}
}
break;
case "ExportSpecifier":
$local = $this->renderNode($node->getLocal());
$ref = $this->renderNode($node->getExported());
$code .= $local === $ref ?
$local :
$local . " as " . $ref;
break;
case "ForInStatement":
case "ForOfStatement":
//Force single line mode for substatements
$this->renderOpts->forceSingleLine = true;
$code .= "for" .
($type === "ForOfStatement" && $node->getAwait() ? " await" : "") .
$this->renderOpts->sao .
"(" .
$this->renderOpts->sirb .
$this->renderNode($node->getLeft()) .
" " . ($type === "ForInStatement" ? "in" : "of") . " " .
$this->renderNode($node->getRight()) .
$this->renderOpts->sirb .
")" .
$this->renderStatementBlock($node->getBody());
unset($this->renderOpts->forceSingleLine);
break;
case "ForStatement":
//Force single line mode for substatements
$this->renderOpts->forceSingleLine = true;
$code .= "for" .
$this->renderOpts->sao .
"(" .
$this->renderOpts->sirb;
if ($init = $node->getInit()) {
$code .= $this->renderNode($init);
}
$code .= ";" . $this->renderOpts->sao;
if ($test = $node->getTest()) {
$code .= $this->renderNode($test);
}
$code .= ";" . $this->renderOpts->sao;
if ($update = $node->getUpdate()) {
$code .= $this->renderNode($update);
}
$code .= $this->renderOpts->sirb .
")" .
$this->renderStatementBlock($node->getBody());
unset($this->renderOpts->forceSingleLine);
break;
case "FunctionDeclaration":
case "FunctionExpression":
$id = $node->getId();
if ($node->getAsync()) {
$code .= "async ";
}
$code .= "function";
if ($node->getGenerator()) {
$code .= $this->renderOpts->sao .
"*";
} elseif ($id) {
$code .= " ";
}
if ($id) {
if ($node->getGenerator()) {
$code .= $this->renderOpts->sao;
}
$code .= $this->renderNode($id);
}
$code .= $this->renderOpts->sao .
"(" .
$this->renderOpts->sirb .
$this->joinNodes(
$node->getParams(),
"," . $this->renderOpts->sao
) .
$this->renderOpts->sirb .
")" .
$this->renderStatementBlock($node->getBody(), true);
break;
case "ImportExpression":
$code .= "import(" .
$this->renderOpts->sirb .
$this->renderNode($node->getSource()) .
$this->renderOpts->sirb .
")";
break;
case "JSXIdentifier":
case "Identifier":
$code .= $node->getRawName();
break;
case "IfStatement":
$code .= "if" .
$this->renderOpts->sao .
"(" .
$this->renderOpts->sirb .
$this->renderNode($node->getTest()) .
$this->renderOpts->sirb .
")";
$consequent = $node->getConsequent();
$alternate = $node->getAlternate();
$forceBracketsConsequent = $forceBracketsAlternate = null;
if (!$this->renderOpts->awb && $alternate) {
$forceBracketsConsequent = $this->checkIfPartsBracketsRequirement(
$consequent
);
$forceBracketsAlternate = $this->checkIfPartsBracketsRequirement(
$alternate
);
}
$code .= $this->renderStatementBlock(
$consequent, $forceBracketsConsequent
);
if ($alternate) {
$code .= $this->renderOpts->sao .
"else" .
$this->renderStatementBlock(
$alternate,
$forceBracketsAlternate,
true
);
}
break;
case "ImportDeclaration":
$code .= "import ";
$specifiers = $node->getSpecifiers();
if (count($specifiers)) {
$sep = "," . $this->renderOpts->sao;
$groups = $parts = array();
foreach ($specifiers as $spec) {
$specType = $spec->getType();
if (!isset($groups[$specType])) {
$groups[$specType] = array();
}
$groups[$specType][] = $spec;
}
if (isset($groups["ImportDefaultSpecifier"])) {
foreach ($groups["ImportDefaultSpecifier"] as $s) {
$parts[] = $this->renderNode($s);
}
}
if (isset($groups["ImportNamespaceSpecifier"])) {
foreach ($groups["ImportNamespaceSpecifier"] as $s) {
$parts[] = $this->renderNode($s);
}
}
if (isset($groups["ImportSpecifier"])) {
$impSpec = array();
foreach ($groups["ImportSpecifier"] as $s) {
$impSpec[] = $this->renderNode($s);
}
$parts[] = "{" . implode($sep, $impSpec) . "}";
}
$code .= implode($sep, $parts) . " from ";
}
$code .= $this->renderNode($node->getSource());
break;
case "ImportDefaultSpecifier":
$code .= $this->renderNode($node->getLocal());
break;
case "ImportNamespaceSpecifier":
$code .= "* as " . $this->renderNode($node->getLocal());
break;
case "ImportSpecifier":
$local = $this->renderNode($node->getLocal());
$ref = $this->renderNode($node->getImported());
$code .= $local === $ref ?
$local :
$ref . " as " . $local;
break;
case "JSXAttribute":
$code .= $this->renderNode($node->getName());
if ($value = $node->getValue()) {
$code .= "=" . $this->renderNode($value);
}
break;
case "JSXClosingElement":
$code .= "</" . $this->renderNode($node->getName()) . ">";
break;
case "JSXClosingFragment":
$code .= "</>";
break;
case "JSXElement":
$code .= $this->renderNode($node->getOpeningElement()) .
$this->joinNodes($node->getChildren(), "");
if ($closing = $node->getClosingElement()) {
$code .= $this->renderNode($closing);
}
break;
case "JSXExpressionContainer":
$code .= "{" . $this->renderNode($node->getExpression()) . "}";
break;
case "JSXFragment":
$code .= $this->renderNode($node->getOpeningFragment()) .
$this->joinNodes($node->getChildren(), "") .
$this->renderNode($node->getClosingFragment());
break;
case "JSXNamespacedName":
$code .= $this->renderNode($node->getNamespace()) . ":" .
$this->renderNode($node->getName());
break;
case "JSXOpeningElement":
$code .= "<" . $this->renderNode($node->getName());
$attributes = $node->getAttributes();
if (count($attributes)) {
$code .= " " . $this->joinNodes($attributes, " ");
}
if ($node->getSelfClosing()) {
$code .= "/";
}
$code .= ">";
break;
case "JSXOpeningFragment":
$code .= "<>";
break;
case "JSXSpreadAttribute":
$code .= "{..." . $this->renderNode($node->getArgument()) . "}";
break;
case "JSXSpreadChild":
$code .= "{..." . $this->renderNode($node->getExpression()) . "}";
break;
case "LabeledStatement":
$body = $node->getBody();
$code .= $this->renderNode($node->getLabel()) .
":";
if ($body->getType() === "BlockStatement") {
$code .= $this->renderStatementBlock($body, true);
} else {
$code .= $this->renderOpts->nl .
$this->getIndentation() .
$this->renderNode($body);
}
if ($this->requiresSemicolon($body)) {
$code .= ";";
}
break;
case "JSXText":
case "Literal":
case "RegExpLiteral":
$code .= $node->getRaw();
break;
case "JSXMemberExpression":
case "MemberExpression":
$property = $node->getProperty();
$compiledProperty = $this->renderNode($property);
$code .= $this->renderNode($node->getObject());
$optional = false;
if ($type === "MemberExpression") {
$optional = $node->getOptional();
}
$propertyType = $property->getType();
if ($type === "MemberExpression" &&
($node->getComputed() ||
($propertyType !== "Identifier" && $propertyType !== "PrivateIdentifier"))) {
$code .= ($optional ? "?." : "") . "[" . $compiledProperty . "]";
} else {
$code .= ($optional ? "?." : ".") . $compiledProperty;
}
break;
case "MetaProperty":
$code .= $node->getMeta() . "." . $node->getProperty();
break;
case "MethodDefinition":
if ($node->getStatic()) {
$code .= "static ";
}
$value = $node->getValue();
$key = $node->getKey();
$kind = $node->getKind();
if ($kind === $node::KIND_GET || $kind === $node::KIND_SET) {
$code .= $kind . " ";
} else {
if ($value->getAsync()) {
$code .= "async ";
}
if ($value->getGenerator()) {
$code .= "*" .
$this->renderOpts->sao;
}
}
if ($node->getComputed()) {
$code .= "[" .
$this->renderNode($key) .
"]";
} else {
$code .= $this->renderNode($key);
}
$code .= $this->renderOpts->sao .
preg_replace("/^[^(]+/", "", $this->renderNode($value));
break;
case "ObjectExpression":
$currentIndentation = $this->getIndentation();
$this->renderOpts->indLevel++;
$indentation = $this->getIndentation();
//Handle single line mode
if (isset($this->renderOpts->forceSingleLine)) {
$start = $end = "";
$separator = "," . $this->renderOpts->sao;
} else {
$end = $this->renderOpts->nl . $currentIndentation;
$start = $this->renderOpts->nl . $indentation;
$separator = "," . $this->renderOpts->nl . $indentation;
}
$code .= "{";
$properties = $node->getProperties();
if (count($properties)) {
$code .= $start .
$this->joinNodes(
$properties,
$separator
) .
$end;
}
$code .= "}";
$this->renderOpts->indLevel--;
break;
case "ObjectPattern":
$code .= "{" .
$this->joinNodes(
$node->getProperties(),
"," . $this->renderOpts->sao
) .
"}";
break;
case "ParenthesizedExpression":
$code .= "(" .
$this->renderOpts->sirb .
$this->renderNode($node->getExpression()) .
$this->renderOpts->sirb .
")";
break;
case "PrivateIdentifier":
$code .= "#" . $node->getName();
break;
case "Property":
$value = $node->getValue();
$key = $node->getKey();
$compiledKey = $this->renderNode($key);
$compiledValue = $this->renderNode($value);
$keyType = $key->getType();
$valueType = $value->getType();
if ($valueType === "AssignmentPattern" &&
$compiledKey === $this->renderNode($value->getLeft())) {
$code .= $compiledValue;
} else {
$kind = $node->getKind();
$getterSetter = $kind === $node::KIND_GET ||
$kind === $node::KIND_SET;
if ($getterSetter) {
$code .= $kind . " ";
} elseif ($value->getType() === "FunctionExpression" &&
$value->getGenerator()) {
$code .= "*" .
$this->renderOpts->sao;
}
if ($node->getComputed()) {
$code .= "[" . $compiledKey . "]";
} else {
$code .= $compiledKey;
}
if ($node->getMethod() || $getterSetter) {
$code .= $this->renderOpts->sao .
preg_replace("/^[^(]+/", "", $compiledValue);
} elseif ($keyType !== "Identifier" ||
$valueType !== "Identifier" ||
$compiledKey !== $compiledValue
) {
$code .= ($node->getShorthand() ? "=" : ":") .
$this->renderOpts->sao .
$compiledValue;
}
}
break;
case "PropertyDefinition":
if ($node->getStatic()) {
$code .= "static ";
}
$compiledKey = $this->renderNode($node->getKey());
if ($node->getComputed()) {
$code .= "[" . $compiledKey . "]";
} else {
$code .= $compiledKey;
}
if ($value = $node->getValue()) {
$code .= $this->renderOpts->sao .
"=" .
$this->renderOpts->sao .
$this->renderNode($value);
}
break;
case "RestElement":
case "SpreadElement":
$code .= "..." . $this->renderNode($node->getArgument());
break;
case "ReturnStatement":
$code .= "return";
if ($argument = $node->getArgument()) {
$code .= " " . $this->renderNode($argument);
}
break;
case "SequenceExpression":
$code .= $this->joinNodes(
$node->getExpressions(),
"," . $this->renderOpts->sao
);
break;
case "StaticBlock":
$code .= "static";
$code .= $this->renderStatementBlock($node->getBody(), true);
break;
case "Super":
$code .= "super";
break;
case "SwitchCase":
if ($test = $node->getTest()) {
$code .= "case " . $this->renderNode($test);
} else {
$code .= "default";
}
$code .= ":";
if (count($node->getConsequent())) {
$code .= $this->renderOpts->nl .
$this->renderStatementBlock(
$node->getConsequent(),
false
);
}
break;
case "SwitchStatement":
$code .= "switch" .
$this->renderOpts->sao .
"(" .
$this->renderOpts->sirb .
$this->renderNode($node->getDiscriminant()) .
$this->renderOpts->sirb .
")" .
$this->renderStatementBlock(
$node->getCases(), true, false, false
);
break;
case "TaggedTemplateExpression":
$code .= $this->renderNode($node->getTag()) .
$this->renderNode($node->getQuasi());
break;
case "TemplateElement":
$code .= $node->getRawValue();
break;
case "TemplateLiteral":
$code .= "`";
foreach ($node->getParts() as $part) {
if ($part->getType() === "TemplateElement") {
$code .= $this->renderNode($part);
} else {
$code .= "$" . "{" . $this->renderNode($part) . "}";
}
}
$code .= "`";
break;
case "ThisExpression":
$code .= "this";
break;
case "ThrowStatement":
$code .= "throw " . $this->renderNode($node->getArgument());
break;
case "TryStatement":
$code .= "try" .
$this->renderStatementBlock($node->getBlock(), true);
if ($handler = $node->getHandler()) {
$code .= $this->renderOpts->sao .
$this->renderNode($handler);
}
if ($finalizer = $node->getFinalizer()) {
$code .= $this->renderOpts->sao .
"finally" .
$this->renderStatementBlock($finalizer, true);
}
break;
case "UnaryExpression":
case "UpdateExpression":
$prefix = $node->getPrefix();
if ($prefix) {
$code .= $node->getOperator();
if (preg_match("#^[a-z]+$#i", $node->getOperator())) {
$code .= " ";
}
}
$code .= $this->renderNode($node->getArgument());
if (!$prefix) {
$code .= $node->getOperator();
}
break;
case "VariableDeclaration":
$this->renderOpts->indLevel++;
$indentation = $this->getIndentation();
//Handle single line mode
if (isset($this->renderOpts->forceSingleLine)) {
$separator = "," . $this->renderOpts->sao;
} else {
$separator = "," . $this->renderOpts->nl . $indentation;
}
$code .= $node->getKind() .
" " .
$this->joinNodes(
$node->getDeclarations(),
$separator
);
$this->renderOpts->indLevel--;
break;
case "VariableDeclarator":
$code .= $this->renderNode($node->getId());
if ($init = $node->getInit()) {
$code .= $this->renderOpts->sao .
"=" .
$this->renderOpts->sao .
$this->renderNode($init);
}
break;
case "WhileStatement":
$code .= "while" .
$this->renderOpts->sao .
"(" .
$this->renderOpts->sirb .
$this->renderNode($node->getTest()) .
$this->renderOpts->sirb .
")" .
$this->renderStatementBlock($node->getBody());
break;
case "WithStatement":
$code .= "with" .
$this->renderOpts->sao .
"(" .
$this->renderOpts->sirb .
$this->renderNode($node->getObject()) .
$this->renderOpts->sirb .
")" .
$this->renderStatementBlock($node->getBody());
break;
case "YieldExpression":
$code .= "yield";
if ($node->getDelegate()) {
$code .= " *";
}
if ($argument = $node->getArgument()) {
$code .= " " . $this->renderNode($argument);
}
break;
}
if ($addSemicolon) {
$code .= ";";
}
if ($this->renderOpts->com) {
$code .= $this->renderComments($node, false);
}
return $code;
}
/**
* Renders a node as a block statement
*
* @param Syntax\Node\Node|array $node Node or array of
* nodes to render
* @param bool $forceBrackets Overrides brackets
* inserting rules
* @param bool $mandatorySeparator True if a starting
* separator is
* mandatory
* @param bool $addSemicolons Semicolons are
* inserted automatically
* if this parameter is
* not false
* @param bool $incIndent If false indentation
* level won't be
* incremented
*
* @return string
*/
protected function renderStatementBlock(
$node, $forceBrackets = null, $mandatorySeparator = false,
$addSemicolons = true, $incIndent = true
) {
$code = "";
//Special handling of BlockStatement and ClassBody nodes by rendering
//their child nodes
$origNode = null;
if (!is_array($node) &&
in_array($node->getType(), array("BlockStatement", "ClassBody"))) {
$origNode = $node;
$node = $node->getBody();
}
//If $forceBrackets is not null use its value to override curly brackets
//insertion rules
if ($forceBrackets !== null) {
$hasBrackets = $forceBrackets;
} else {
//Insert curly brackets if required by the formatter or if the
//number of nodes to render is different from one
$hasBrackets = $this->renderOpts->awb ||
(is_array($node) && count($node) !== 1);
}
$currentIndentation = $this->getIndentation();
//If $forceBrackets is not set to false then the node can be wrapped in
//curly braces, so a separator defined by formatter must be inserted
if ($forceBrackets !== false) {
if ($this->renderOpts->nlbc) {
$code .= $this->renderOpts->nl . $currentIndentation;
} else {
$code .= $this->renderOpts->sao;
}
}
$emptyBody = is_array($node) && !count($node);
if ($this->renderOpts->com && $origNode) {
$code .= $this->renderComments($origNode, true, !$emptyBody);
}
//Insert open curly bracket if required
if ($hasBrackets) {
$code .= "{" . $this->renderOpts->nl;
} elseif ($mandatorySeparator) {
//If bracket is not inserted but a separator is still required
//a space is added
$code .= " ";
}
//Increase indentation level
if ($incIndent) {
$this->renderOpts->indLevel++;
}
$subIndentation = $this->getIndentation();
//Render the node or the array of nodes
if (is_array($node)) {
if (!$emptyBody) {
$code .= $subIndentation .
$this->joinNodes(
$node,
$this->renderOpts->nl . $subIndentation,
$addSemicolons
);
}
} else {
$code .= $subIndentation . $this->renderNode(
$node,
$addSemicolons && $this->requiresSemicolon($node)
);
}
if ($this->renderOpts->com) {
//Strip last new line and indentations added by comments rendering
if (!$emptyBody) {
$code = $this->trimEmptyLine($code);
}
if ($origNode) {
$code .= $this->renderComments($origNode, false, !$emptyBody);
if (!$emptyBody) {
$code = $this->trimEmptyLine($code);
}
}
}
//Reset the indentation level
if ($incIndent) {
$this->renderOpts->indLevel--;
}
//Insert closing curly bracket if required
if ($hasBrackets) {
//Add a new line if something was rendered
if (!$emptyBody) {
$code .= $this->renderOpts->nl;
}
$code .= $currentIndentation . "}";
}
return $code;
}
/**
* Joins an array of nodes with the given separator
*
* @param array $nodes Nodes
* @param string $separator Separator
* @param bool $addSemicolons True to add semicolons after each node
*
* @return string
*/
protected function joinNodes($nodes, $separator, $addSemicolons=false)
{
$parts = array();
foreach ($nodes as $node) {
if (!$node) {
$code = "";
} else {
$code = $this->renderNode(
$node,
$addSemicolons && $this->requiresSemicolon($node)
);
}
$parts[] = $code;
}
return implode($separator, $parts);
}
/**
* Checks if consequent and alternate nodes of an IfStatement require the
* mandatory insertion of brackets around them to avoid the pattern where
* the "else" code is rendered as part of an inner IfStatement.
*
* @param Syntax\Node\Node $node Node
*
* @return null|bool
*/
protected function checkIfPartsBracketsRequirement($node)
{
if ($node->getType() === "BlockStatement" && count($node->getBody()) > 1) {
return null;
}
$forceBrackets = null;
$optBracketNodes = array(
"DoWhileStatement", "ForInStatement", "ForOfStatement",
"ForStatement", "WhileStatement", "WithStatement"
);
$checkFn = function ($n) use ($optBracketNodes, &$forceBrackets) {
$type = $n->getType();
if ($type === "IfStatement") {
if (!$n->getAlternate()) {
$forceBrackets = true;
}
return Traverser::DONT_TRAVERSE_CHILD_NODES;
} elseif ($type === "BlockStatement") {
if (count($n->getBody()) !== 1) {
return Traverser::DONT_TRAVERSE_CHILD_NODES;
}
} elseif ($type === "LabeledStatement") {
if ($n->getBody()->getType() === "BlockStatement") {
$forceBrackets = true;
}
return Traverser::DONT_TRAVERSE_CHILD_NODES;
} elseif (!in_array($type, $optBracketNodes)) {
return Traverser::DONT_TRAVERSE_CHILD_NODES;
}
};
$node->traverse($checkFn);
return $forceBrackets;
}
/**
* Render node's comments
*
* @param Syntax\Node\Node $node Node
* @param bool $leading False to render trailing comments
* @param bool|null $blockContent This paramater can have 3 values:
* - null: the node is not a block
* - false: the node is an empty block
* - true: the node is a block with content
*
* @return string
*/
protected function renderComments($node, $leading = true, $blockContent = null)
{
$code = "";
$fn = $leading ? "getLeadingComments" : "getTrailingComments";
$comments = $node ? $node->$fn() : array();
$numComments = count($comments);
if ($numComments) {
$lastFormatted = $blockContent === false ? true : $leading;
$refNode = $node;
$refKey = $leading ? "end" : "start";
$refNodeKey = $leading ? "start" : "end";
$indent = $this->getIndentation();
foreach ($comments as $k => $comment) {
$lastComment = $k === $numComments - 1;
$isMultilineComment = $comment->getKind() === Comment::KIND_MULTILINE;
// Check if the comment must be formatted with new line and indentations
$format = true;
if (
$refNode && $isMultilineComment &&
$comment->location && $refNode->location &&
$comment->location->$refKey->getLine() === $refNode->location->$refNodeKey->getLine()
) {
$format = false;
}
//If the last comment wasn't formatted but this one must be formatted, add the new
//line and the indentation
if ($format && !$lastFormatted) {
$code .= $this->renderOpts->nl . $indent;
}
//Leading comments on empty blocks must render the initial indentation if format
//is enabled
if ($format && $blockContent === false && !$k) {
$code .= $indent;
}
$commentRaw = $comment->getRawText();
//Reindent multiline comments if necessary
if ($isMultilineComment && $indent && $this->renderOpts->rci) {
$commentRaw = preg_replace("/^\s+\*/m", $indent . " *", $commentRaw);
}
$code .= $commentRaw;
//If format is enabled, add the new line character and the indentation if the node
//is not an empty block or the it's not the last comment
if ($format && ($blockContent !== true || !$lastComment || !$isMultilineComment)) {
//For non multiline comments the new line is mandatory, even if the formatter
//disables it
$code .= !$isMultilineComment && !$this->renderOpts->nl ? "\n" : $this->renderOpts->nl;
//Last comment on blocks must not render indentation
if ($blockContent === null || !$lastComment) {
$code .= $indent;
}
}
$refNode = $comment;
$lastFormatted = $format;
}
}
return $code;
}
/**
* Removes an empty line at the end of the given code, if present
*
* @param string $code Code
*
* @return string
*/
protected function trimEmptyLine($code)
{
if ($this->renderOpts->nl) {
$nl = preg_quote($this->renderOpts->nl, "/");
$indent = preg_quote($this->getIndentation(), "/");
$code = preg_replace("/$nl(?:$indent)?$/", "", $code);
}
return $code;
}
/**
* Check if the given node requires semicolons insertion
*
* @param Syntax\Node\Node $node Node
*
* @return bool
*/
protected function requiresSemicolon($node)
{
return !in_array($node->getType(), $this->noSemicolon);
}
/**
* Returns the current indentation string
*
* @return string
*/
protected function getIndentation()
{
return str_repeat(
$this->renderOpts->ind,
$this->renderOpts->indLevel
);
}
}