1195 lines
46 KiB
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
|
|
);
|
|
}
|
|
} |