lib/AST2JS/AST2JS.cpp (941 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "hermes/AST2JS/AST2JS.h"
#include "hermes/AST/RecursiveVisitor.h"
#include "hermes/Support/Conversions.h"
#include "hermes/Support/UTF8.h"
#include "llvh/ADT/StringSwitch.h"
#include "llvh/Support/Format.h"
namespace hermes {
using namespace ESTree;
namespace {
enum Precedence {
kAlwaysParenPrec = 0,
kSeqPrec,
kArrowPrec,
kYieldPrec,
kAssignPrec,
kCondPrec,
kBinPrecStart,
kBinPrecEnd = kBinPrecStart + 20,
kUnaryPrec,
kPostUpdatePrec,
kTaggedTemplatePrec,
kNewNoArgsPrec,
kMemberPrec,
kPrimaryPrec,
kTopPrec,
};
Precedence getBinaryPrecedence(llvh::StringRef op) {
return llvh::StringSwitch<Precedence>(op)
#define BINOP(name, str, prec) .Case(str, (Precedence)(prec + kBinPrecStart))
#include "hermes/Parser/TokenKinds.def"
.Case("in", (Precedence)(8 + kBinPrecStart))
.Case("instanceof", (Precedence)(8 + kBinPrecStart))
.Default(kAlwaysParenPrec);
}
/// Precedence and associativity of an operator.
struct PrecRTL {
Precedence prec; // Higher value means higher precedence.
bool rtl; // right to left.
/* implicit */ PrecRTL(Precedence prec, bool rtl = false)
: prec(prec), rtl(rtl) {}
};
/// Child position for the purpose of determining whether the child needs
/// parenthesis.
enum class ChildPos {
left,
anywhere,
right,
};
/// Whether parenthesis are needed around something.
enum class NeedParens {
// Parens are not needed.
no,
// Parens are needed.
yes,
// Space is sufficient.
space
};
NeedParens np(bool x) {
return x ? NeedParens::yes : NeedParens::no;
}
class GenJS {
/// Output the generated JS here.
llvh::raw_ostream &OS_;
/// Pretty print or not.
bool const pretty_;
/// Indentation step. In the future it may be configurable.
static constexpr int indentStep_ = 2;
/// Current indentation level. Only used in "pretty" mode.
int indent_ = 0;
public:
GenJS(llvh::raw_ostream &OS, bool const pretty) : OS_(OS), pretty_(pretty) {}
void doIt(Node *root) {
visitESTreeNode(*this, root);
OS_ << '\n';
}
/// Needed by RecursiveVisitorDispatch. Optionally can protect against too
/// deep nesting.
bool incRecursionDepth(Node *) {
return true;
}
void decRecursionDepth() {}
// TODO: remove this catch all function to find out which visitors need to be
// TODO: defined at compile time.
void visit(Node *n) {
llvh::errs() << "Unsupported AST node kind: " << n->getNodeName() << " ";
abort();
}
void visit(IdentifierNode *node) {
OS_ << node->_name->str();
}
void visit(NullLiteralNode *) {
OS_ << "null";
}
void visit(BooleanLiteralNode *node) {
OS_ << (node->_value ? "true" : "false");
}
void visit(StringLiteralNode *node) {
OS_ << '"';
printEscapedStringContents(node->_value->str(), '"');
OS_ << '"';
}
void visit(RegExpLiteralNode *node) {
// FIXME: escaping, etc?
OS_ << '/';
OS_ << node->_pattern->str();
OS_ << '/';
OS_ << node->_flags->str();
}
void visit(TemplateLiteralNode *node) {
constexpr llvh::StringLiteral esc2("${");
OS_ << '`';
auto ecur = node->_expressions.begin();
auto eend = node->_expressions.end();
for (auto &_elem : node->_quasis) {
auto *elem = llvh::cast<TemplateElementNode>(&_elem);
OS_ << elem->_raw->str();
if (ecur != eend) {
OS_ << esc2;
visitESTreeNode(*this, &*ecur++, node);
OS_ << '}';
}
}
OS_ << '`';
}
void visit(NumericLiteralNode *node) {
char buf8[hermes::NUMBER_TO_STRING_BUF_SIZE];
size_t len = hermes::numberToString(node->_value, buf8, sizeof(buf8));
OS_.write(buf8, len);
}
void visit(ThisExpressionNode *) {
OS_ << "this";
}
void visit(SuperNode *) {
OS_ << "super";
}
void visit(TaggedTemplateExpressionNode *node) {
printChild(node->_tag, node, ChildPos::left);
printChild(node->_quasi, node, ChildPos::right);
}
void visit(SequenceExpressionNode *node) {
int i = 0;
for (auto &expr : node->_expressions) {
if (i++)
comma();
printChild(&expr, node, i == 1 ? ChildPos::left : ChildPos::right);
}
}
void visit(ObjectExpressionNode *node) {
visitProps(node->_properties, node);
}
void visit(ObjectPatternNode *node) {
visitProps(node->_properties, node);
}
void visitProps(NodeList &props, Node *parent) {
OS_ << '{';
int i = 0;
for (auto &prop : props) {
if (i++)
comma();
visitESTreeNode(*this, &prop, parent);
}
OS_ << '}';
}
void visit(PropertyNode *node) {
bool needSep = false;
if (node->_kind->str() != "init") {
// get/set
OS_ << node->_kind->str();
needSep = true;
} else if (node->_method) {
auto *fe = llvh::cast<FunctionExpressionNode>(node->_value);
if (fe->_async) {
OS_ << "async";
needSep = true;
}
if (fe->_generator) {
OS_ << '*';
needSep = false;
space();
}
}
if (node->_computed) {
if (needSep)
space();
needSep = false;
OS_ << '[';
}
if (needSep)
OS_ << ' ';
needSep = false;
visitESTreeNode(*this, node->_key);
if (node->_computed)
OS_ << ']';
if (node->_shorthand)
return;
if (node->_kind->str() != "init" || node->_method) {
// getter/setter or method.
auto *fe = llvh::cast<FunctionExpressionNode>(node->_value);
visitFuncParamsAndBody(fe->_params, fe->_body, fe);
} else {
// Regular property.
OS_ << ':';
space();
printCommaExpression(node->_value, node);
}
}
void visit(ArrayExpressionNode *node) {
OS_ << '[';
int i = 0;
for (auto &elem : node->_elements) {
if (i++ != 0)
comma();
printCommaExpression(&elem, node);
}
if (node->_trailingComma)
comma();
OS_ << ']';
}
void visit(CallExpressionNode *node) {
printChild(node->_callee, node, ChildPos::left);
OS_.write('(');
int i = 0;
for (auto &arg : node->_arguments) {
if (i++ != 0)
comma();
printCommaExpression(&arg, node);
}
OS_.write(')');
}
void visit(NewExpressionNode *node) {
OS_ << "new ";
printChild(node->_callee, node, ChildPos::anywhere);
OS_.write('(');
int i = 0;
for (auto &arg : node->_arguments) {
if (i++ != 0)
comma();
printCommaExpression(&arg, node);
}
OS_.write(')');
}
void visit(AssignmentExpressionNode *node) {
printChild(node->_left, node, ChildPos::left);
space();
OS_ << node->_operator->str();
space();
printChild(node->_right, node, ChildPos::right);
}
void visit(UnaryExpressionNode *node) {
bool const ident = isalpha(node->_operator->str().front());
OS_ << node->_operator->str();
if (ident)
OS_ << ' ';
printChild(node->_argument, node, ChildPos::right);
}
void visit(UpdateExpressionNode *node) {
if (node->_prefix) {
OS_ << node->_operator->str();
printChild(node->_argument, node, ChildPos::right);
} else {
printChild(node->_argument, node, ChildPos::left);
OS_ << node->_operator->str();
}
}
void visit(MemberExpressionNode *node) {
visitESTreeNode(*this, node->_object, node);
if (node->_computed)
OS_ << '[';
else
OS_ << '.';
visitESTreeNode(*this, node->_property, node);
if (node->_computed)
OS_ << ']';
}
void visit(LogicalExpressionNode *node) {
printChild(node->_left, node, ChildPos::left);
space();
OS_ << node->_operator->str();
space();
printChild(node->_right, node, ChildPos::right);
}
void visit(BinaryExpressionNode *node) {
bool const ident = isalpha(node->_operator->str().front());
printChild(node->_left, node, ChildPos::left);
space(ident);
OS_ << node->_operator->str();
space(ident);
printChild(node->_right, node, ChildPos::right);
}
void visit(ConditionalExpressionNode *node) {
printChild(node->_test, node, ChildPos::left);
space();
OS_ << '?';
space();
printChild(node->_consequent, node, ChildPos::anywhere);
space();
OS_ << ':';
space();
printChild(node->_alternate, node, ChildPos::right);
}
void visit(VariableDeclarationNode *node) {
OS_ << node->_kind->str() << ' ';
int i = 0;
for (auto &decl : node->_declarations) {
if (i++ != 0)
comma();
visitESTreeNode(*this, &decl, node);
}
}
void visit(VariableDeclaratorNode *node) {
visitESTreeNode(*this, node->_id, node);
if (node->_init) {
OS_ << (pretty_ ? " = " : "=");
visitESTreeNode(*this, node->_init, node);
}
}
void visit(ThrowStatementNode *node) {
OS_ << "throw ";
visitESTreeNode(*this, node->_argument, node);
}
void visit(ReturnStatementNode *node) {
OS_ << "return";
if (node->_argument) {
OS_ << ' ';
visitESTreeNode(*this, node->_argument, node);
}
}
void visit(WhileStatementNode *node) {
OS_ << "while";
space();
OS_ << '(';
visitESTreeNode(*this, node->_test, node);
OS_ << ')';
visitStmtOrBlock(node->_body, false, node);
}
void visit(DoWhileStatementNode *node) {
OS_ << "do";
bool block = visitStmtOrBlock(node->_body, false, node);
if (block) {
space();
} else {
OS_ << ";";
newline();
}
OS_ << "while";
space();
OS_ << '(';
visitESTreeNode(*this, node->_test, node);
OS_ << ')';
}
void visit(IfStatementNode *node) {
OS_ << "if";
space();
OS_ << '(';
visitESTreeNode(*this, node->_test, node);
OS_ << ')';
bool forceBlock = node->_alternate && isIfWithoutElse(node->_consequent);
bool block = visitStmtOrBlock(node->_consequent, forceBlock, node);
if (!node->_alternate)
return;
if (!block) {
OS_ << ";";
newline();
} else {
space();
}
OS_ << "else";
visitStmtOrBlock(node->_alternate, false, node);
}
/// Visit a statement node which is the body of a loop or a clause in an if.
/// It could be a block statement.
/// \return true if block
bool visitStmtOrBlock(Node *node, bool forceBlock, Node *parent) {
auto *block = llvh::dyn_cast<BlockStatementNode>(node);
if ((block && block->_body.empty()) ||
llvh::isa<EmptyStatementNode>(node)) {
space();
OS_ << "{}";
return true;
}
if (block || forceBlock) {
space();
OS_ << '{';
incIndent();
newline();
if (block)
visitStmtList(block->_body, block);
else
visitStmtInABlock(node, parent);
decIndent();
newline();
OS_ << "}";
return true;
} else {
incIndent();
newline();
visitESTreeNode(*this, node, parent);
decIndent();
return false;
}
}
void visit(EmptyStatementNode *) {}
void visit(ForStatementNode *node) {
OS_ << "for(";
printChild(node->_init, node, ChildPos::left);
OS_ << ';';
if (node->_test) {
space();
visitESTreeNode(*this, node->_test, node);
}
OS_ << ';';
if (node->_update) {
space();
visitESTreeNode(*this, node->_update, node);
}
OS_ << ')';
visitStmtOrBlock(node->_body, false, node);
}
void visit(ForInStatementNode *node) {
visitForInOf("in", node->_left, node->_right, node->_body, node);
}
void visit(ForOfStatementNode *node) {
visitForInOf("of", node->_left, node->_right, node->_body, node);
}
void visitForInOf(
llvh::StringRef op,
Node *left,
Node *right,
Node *body,
Node *node) {
OS_ << "for(";
visitESTreeNode(*this, left, node);
OS_ << ' ' << op << ' ';
visitESTreeNode(*this, right, node);
OS_ << ')';
visitStmtOrBlock(body, false, node);
}
void visit(TryStatementNode *node) {
OS_ << "try";
visitStmtOrBlock(node->_block, true, node);
visitESTreeNode(*this, node->_handler, node);
if (node->_finalizer)
visitStmtOrBlock(node->_finalizer, true, node);
}
void visit(CatchClauseNode *node) {
space();
OS_ << "catch";
if (node->_param) {
space();
OS_ << '(';
visitESTreeNode(*this, node->_param, node);
OS_ << ')';
}
visitStmtOrBlock(node->_body, true, node);
}
void visit(SwitchStatementNode *node) {
OS_ << "switch";
space();
OS_ << '(';
visitESTreeNode(*this, node->_discriminant, node);
OS_ << ')';
space();
OS_ << '{';
newline();
for (auto &c : node->_cases) {
visitESTreeNode(*this, &c, node);
newline();
}
OS_ << '}';
}
void visit(SwitchCaseNode *node) {
if (node->_test) {
OS_ << "case ";
visitESTreeNode(*this, node->_test, node);
} else {
OS_ << "default";
}
OS_ << ':';
if (!node->_consequent.empty()) {
incIndent();
newline();
visitStmtList(node->_consequent, node);
decIndent();
}
}
void visit(BreakStatementNode *node) {
OS_ << "break";
if (node->_label) {
OS_ << ' ';
visitESTreeNode(*this, node->_label, node);
}
}
void visit(ContinueStatementNode *node) {
OS_ << "continue";
if (node->_label) {
OS_ << ' ';
visitESTreeNode(*this, node->_label, node);
}
}
void visit(LabeledStatementNode *node) {
visitESTreeNode(*this, node->_label, node);
OS_ << ':';
newline();
visitESTreeNode(*this, node->_body, node);
}
void visit(BlockStatementNode *node) {
if (node->_body.empty()) {
OS_ << "{}";
return;
}
OS_ << '{';
incIndent();
newline();
visitStmtList(node->_body, node);
decIndent();
newline();
OS_ << '}';
}
void visit(ProgramNode *node) {
visitStmtList(node->_body, node);
}
void visitStmtInABlock(Node *stmt, Node *parent) {
visitESTreeNode(*this, stmt, parent);
if (!endsWithBlock(stmt))
OS_ << ";";
}
void visitStmtList(NodeList &list, Node *parent) {
int i = 0;
for (auto &stmt : list) {
if (i++)
newline();
visitStmtInABlock(&stmt, parent);
}
}
void visit(ClassExpressionNode *node) {
visitClass(node->_id, node->_superClass, node->_body, node);
}
void visit(ClassDeclarationNode *node) {
visitClass(node->_id, node->_superClass, node->_body, node);
}
void visitClass(Node *id, Node *superClass, Node *body, Node *parent) {
OS_ << "class";
if (id) {
OS_ << ' ';
visitESTreeNode(*this, id, parent);
}
if (superClass) {
OS_ << ' ';
visitESTreeNode(*this, superClass, parent);
}
space();
visitESTreeNode(*this, body, parent);
}
void visit(ClassBodyNode *node) {
OS_ << '{';
if (node->_body.empty()) {
OS_ << '}';
return;
}
incIndent();
newline();
for (auto &p : node->_body) {
visitESTreeNode(*this, &p, node);
newline();
}
OS_ << '}';
decIndent();
}
void visit(FunctionExpressionNode *node) {
visitFuncLike(
node->_async,
node->_generator,
node->_id,
node->_params,
node->_body,
node);
}
void visit(FunctionDeclarationNode *node) {
visitFuncLike(
node->_async,
node->_generator,
node->_id,
node->_params,
node->_body,
node);
}
void visit(ArrowFunctionExpressionNode *node) {
bool needSep = false;
if (node->_async) {
OS_ << "async";
needSep = true;
}
// Single parameter doesn't need parens. But only in expression mode,
// otherwise it is ugly.
if ((node->_expression || !pretty_) && node->_params.size() == 1) {
if (needSep)
OS_ << ' ';
visitESTreeNode(*this, &node->_params.front(), node);
} else {
OS_ << '(';
int i = 0;
for (auto ¶m : node->_params) {
if (i++)
comma();
visitESTreeNode(*this, ¶m, node);
}
OS_ << ')';
}
space();
OS_ << "=>";
space();
if (llvh::isa<BlockStatementNode>(node->_body))
visitESTreeNode(*this, node->_body, node);
else
printChild(node->_body, node, ChildPos::right);
}
void visitFuncLike(
bool async,
bool generator,
Node *id,
NodeList ¶ms,
Node *body,
Node *node) {
if (async)
OS_ << "async ";
OS_ << "function";
if (generator) {
OS_ << '*';
if (id)
space();
} else {
if (id)
OS_ << ' ';
}
if (id)
visitESTreeNode(*this, id, node);
visitFuncParamsAndBody(params, body, node);
}
void visitFuncParamsAndBody(NodeList ¶ms, Node *body, Node *node) {
OS_ << '(';
int i = 0;
for (auto ¶m : params) {
if (i++ != 0)
comma();
visitESTreeNode(*this, ¶m, node);
}
OS_ << ')';
visitESTreeNode(*this, body, node);
}
void visit(ExpressionStatementNode *node) {
printChild(node->_expression, node, ChildPos::anywhere);
}
private:
void incIndent() {
indent_ += indentStep_;
assert(indent_ > 0 && "Impressive! Indent overflow.");
}
void decIndent() {
indent_ -= indentStep_;
assert(indent_ >= 0 && "indent underflow");
}
void comma() {
OS_ << (pretty_ ? ", " : ",");
}
void space(bool force = false) {
if (force || pretty_)
OS_.write(' ');
}
void newline() {
if (pretty_)
OS_.write('\n').indent(indent_);
}
void printChild(Node *child, Node *parent, ChildPos childPosition) {
if (!child)
return;
printParens(child, parent, needParens(parent, child, childPosition));
}
/// Print one expression in a sequence separated by comma. It needs parens
/// if its precedence is <= comma.
void printCommaExpression(Node *child, Node *parent) {
printParens(
child,
parent,
getPrecedence(child).prec <= kSeqPrec ? NeedParens::yes
: NeedParens::no);
}
void printParens(Node *child, Node *parent, NeedParens needParens) {
if (needParens == NeedParens::yes)
OS_ << '(';
else if (needParens == NeedParens::space)
OS_ << ' ';
visitESTreeNode(*this, child, parent);
if (needParens == NeedParens::yes)
OS_ << ')';
}
/// Print the escaped contents of the specified string.
/// \param esc1 Additional character to escape. This is the string separator.
/// \param esc2 An optional string to escape. It this string is non-empty and
/// it is encountered, its first character will be escaped.
void printEscapedStringContents(
llvh::StringRef str,
char esc1,
llvh::StringRef esc2 = {});
/// \return the precendence and associativity of an operator.
PrecRTL getPrecedence(Node *node);
/// Check whether we need parens for the child, when rendered in the parent
/// @param childPosition where the child is printed relative to the parent.
NeedParens needParens(Node *parent, Node *child, ChildPos childPosition);
/// \return true if the node (which could be null) is an IfStatement without
/// an else.
static bool isIfWithoutElse(Node *node) {
auto *ifN = llvh::dyn_cast_or_null<IfStatementNode>(node);
return ifN && !ifN->_alternate;
}
/// \return true if the AST expression starts with a node satisfying the
/// predicate \pred when rendered. This involves recursively checking the
/// left children.
/// \param parent only set when the function is calling itself recursively
/// with the parent of the \c expr node.
bool _exprStartsWith(Node *expr, Node *parent, bool (*pred)(Node *));
/// \return true if the AST expression starts with a node satisfying the
/// predicate \pred when rendered. This involves recursively checking the
/// left children.
bool exprStartsWith(Node *expr, bool (*pred)(Node *)) {
return _exprStartsWith(expr, nullptr, pred);
}
/// \return true if the node ends with a block (so we don't need to generate
/// a semi-colon).
static bool endsWithBlock(Node *node);
};
void GenJS::printEscapedStringContents(
llvh::StringRef str,
char esc1,
llvh::StringRef esc2) {
for (const char *cur = str.data(), *e = str.data() + str.size(); cur < e;) {
uint32_t cp = decodeUTF8<true>(cur, [](const llvh::Twine &) {});
// Check for classic escapes first.
switch (cp) {
case '\\':
OS_ << "\\\\";
continue;
case '\b':
OS_ << "\\b";
continue;
case '\f':
OS_ << "\\f";
continue;
case '\n':
OS_ << "\\n";
continue;
case '\r':
OS_ << "\\r";
continue;
case '\t':
OS_ << "\\t";
continue;
case '\v':
OS_ << "\\v";
continue;
}
// Check for escaping.
if (cp == (unsigned char)esc1) {
OS_ << '\\' << esc1;
continue;
;
}
if (!esc2.empty() && cp == (unsigned char)esc2.front()) {
if (str.substr(cur - str.data() - 1, esc2.size()) == esc2) {
OS_ << '\\' << esc2;
cur += esc2.size() - 1;
continue;
}
}
// Printable.
if (cp >= 0x20 && cp <= 0x7f) {
OS_.write((char)cp);
continue;
}
// Non-printable single byte
if (cp < 0x100) {
OS_ << "\\x" << llvh::format_hex_no_prefix(cp, 2);
continue;
}
// Non-printable two bytes.
if (cp < 0x10000) {
OS_ << "\\u" << llvh::format_hex_no_prefix(cp, 4);
continue;
}
// utf-32 encoded. Decode into a surrogate pair.
uint16_t u16buf[2], *p = u16buf;
encodeUTF16(p, cp);
OS_ << "\\u" << llvh::format_hex_no_prefix(u16buf[0], 4) << "\\u"
<< llvh::format_hex_no_prefix(u16buf[1], 4);
}
}
PrecRTL GenJS::getPrecedence(Node *node) {
// Precedence order taken from
// https://github.com/facebook/flow/blob/master/src/parser_utils/output/js_layout_generator.ml
if (llvh::isa<IdentifierNode>(node) || llvh::isa<NullLiteralNode>(node) ||
llvh::isa<BooleanLiteralNode>(node) ||
llvh::isa<StringLiteralNode>(node) ||
llvh::isa<NumericLiteralNode>(node) ||
llvh::isa<RegExpLiteralNode>(node) ||
llvh::isa<ThisExpressionNode>(node) || llvh::isa<SuperNode>(node) ||
llvh::isa<ArrayExpressionNode>(node) ||
llvh::isa<ObjectExpressionNode>(node) ||
llvh::isa<ObjectPatternNode>(node) ||
llvh::isa<FunctionExpressionNode>(node) ||
llvh::isa<ClassExpressionNode>(node) ||
llvh::isa<TemplateLiteralNode>(node)) {
return kPrimaryPrec;
}
if (llvh::isa<MemberExpressionNode>(node) ||
llvh::isa<MetaPropertyNode>(node) ||
llvh::isa<CallExpressionNode>(node) ||
// `new foo()` has higher precedence than `new foo`. In pretty mode we
// always append the `()`, but otherwise we must check the number of args.
(llvh::isa<NewExpressionNode>(node) &&
(pretty_ || !llvh::cast<NewExpressionNode>(node)->_arguments.empty()))) {
return kMemberPrec;
}
if (llvh::isa<NewExpressionNode>(node)) {
return kNewNoArgsPrec;
}
if (llvh::isa<TaggedTemplateExpressionNode>(node) ||
llvh::isa<ImportExpressionNode>(node)) {
return kTaggedTemplatePrec;
}
if (llvh::isa<UpdateExpressionNode>(node) &&
!llvh::cast<UpdateExpressionNode>(node)->_prefix) {
return kPostUpdatePrec;
}
if (llvh::isa<UpdateExpressionNode>(node) ||
llvh::isa<UnaryExpressionNode>(node)) {
return {kUnaryPrec, true};
}
if (auto *bin = llvh::dyn_cast<BinaryExpressionNode>(node))
return getBinaryPrecedence(bin->_operator->str());
if (auto *bin = llvh::dyn_cast<LogicalExpressionNode>(node))
return getBinaryPrecedence(bin->_operator->str());
if (llvh::isa<ConditionalExpressionNode>(node))
return {kCondPrec, true};
if (llvh::isa<AssignmentExpressionNode>(node))
return {kAssignPrec, true};
if (llvh::isa<YieldExpressionNode>(node))
return kYieldPrec;
if (llvh::isa<ArrowFunctionExpressionNode>(node))
return kYieldPrec;
if (llvh::isa<SequenceExpressionNode>(node))
return {kSeqPrec, true};
return kAlwaysParenPrec;
}
static bool isUnary(Node *node, llvh::StringRef op) {
return llvh::isa<UnaryExpressionNode>(node) &&
llvh::cast<UnaryExpressionNode>(node)->_operator->str() == op;
}
static bool isUpdatePrefix(Node *node, llvh::StringRef op) {
return llvh::isa<UpdateExpressionNode>(node) &&
llvh::cast<UpdateExpressionNode>(node)->_prefix &&
llvh::cast<UpdateExpressionNode>(node)->_operator->str() == op;
}
static bool isNegativeNumber(Node *node) {
return llvh::isa<NumericLiteralNode>(node) &&
llvh::cast<NumericLiteralNode>(node)->_value < 0;
}
static bool isBinary(Node *node, llvh::StringRef op) {
return llvh::isa<BinaryExpressionNode>(node) &&
llvh::cast<BinaryExpressionNode>(node)->_operator->str() == op;
}
static bool checkPlus(Node *node) {
return isUnary(node, "+") || isUpdatePrefix(node, "++");
}
static bool checkMinus(Node *node) {
return isUnary(node, "-") || isUpdatePrefix(node, "--") ||
isNegativeNumber(node);
}
NeedParens
GenJS::needParens(Node *parent, Node *child, ChildPos childPosition) {
// The explicit checks taken from
// https://github.com/facebook/flow/blob/master/src/parser_utils/output/js_layout_generator.ml
// (x) => ({x: 10}) needs parens to avoid confusing it with a block and a
// labelled statement.
if (llvh::isa<ArrowFunctionExpressionNode>(parent)) {
if (childPosition == ChildPos::right &&
llvh::isa<ObjectExpressionNode>(child)) {
return NeedParens::yes;
}
}
// for((a in b);..;..) needs parens to avoid confusing it with for(a in b).
else if (llvh::isa<ForStatementNode>(parent)) {
return np(
llvh::isa<BinaryExpressionNode>(child) &&
llvh::cast<BinaryExpressionNode>(child)->_operator->str() == "in");
}
// Expression statement like (function () {} + 1) needs parens.
else if (llvh::isa<ExpressionStatementNode>(parent)) {
return np(exprStartsWith(child, [](Node *expr) {
return llvh::isa<FunctionExpressionNode>(expr) ||
llvh::isa<ClassExpressionNode>(expr) ||
llvh::isa<ObjectExpressionNode>(expr) ||
llvh::isa<ObjectPatternNode>(expr);
}));
}
// -(-x) or -(--x) or -(-5)
else if (isUnary(parent, "-") && exprStartsWith(child, checkMinus)) {
return pretty_ ? NeedParens::yes : NeedParens::space;
}
// +(+x) or +(++x)
else if (isUnary(parent, "+") && exprStartsWith(child, checkPlus)) {
return pretty_ ? NeedParens::yes : NeedParens::space;
}
// a-(-x) or a-(--x) or a-(-5)
else if (
childPosition == ChildPos::right && isBinary(parent, "-") &&
exprStartsWith(child, checkMinus)) {
return pretty_ ? NeedParens::yes : NeedParens::space;
}
// a+(+x) or a+(++x)
else if (
childPosition == ChildPos::right && isBinary(parent, "+") &&
exprStartsWith(child, checkPlus)) {
return pretty_ ? NeedParens::yes : NeedParens::space;
}
auto childPr = getPrecedence(child);
if (childPr.prec == kAlwaysParenPrec)
return NeedParens::yes;
auto parentPr = getPrecedence(parent);
if (childPr.prec < parentPr.prec) {
// Child is definitely a danger.
return NeedParens::yes;
}
if (childPr.prec > parentPr.prec) {
// Definitely cool.
return NeedParens::no;
}
// Equal precedence, so associativity (rtl/ltr) is what matters.
if (childPosition == ChildPos::anywhere) {
// Child could be anywhere, so always paren.
return NeedParens::yes;
}
if (childPr.prec == kTopPrec) {
// Both precedences are safe.
return NeedParens::no;
}
// Check if child is on the dangerous side.
return parentPr.rtl ? np(childPosition == ChildPos::left)
: np(childPosition == ChildPos::right);
}
bool GenJS::_exprStartsWith(Node *expr, Node *parent, bool (*pred)(Node *)) {
// NOTE: I hope this function is converted into a loop.
if (parent && needParens(parent, expr, ChildPos::left) == NeedParens::yes)
return false;
if (pred(expr))
return true;
if (auto *call = llvh::dyn_cast<CallExpressionNode>(expr))
return _exprStartsWith(call->_callee, expr, pred);
if (auto *call = llvh::dyn_cast<OptionalCallExpressionNode>(expr))
return _exprStartsWith(call->_callee, expr, pred);
if (auto *bin = llvh::dyn_cast<BinaryExpressionNode>(expr))
return _exprStartsWith(bin->_left, expr, pred);
if (auto *bin = llvh::dyn_cast<LogicalExpressionNode>(expr))
return _exprStartsWith(bin->_left, expr, pred);
if (auto *cond = llvh::dyn_cast<ConditionalExpressionNode>(expr))
return _exprStartsWith(cond->_test, expr, pred);
if (auto *ass = llvh::dyn_cast<AssignmentExpressionNode>(expr))
return _exprStartsWith(ass->_left, expr, pred);
if (auto *upd = llvh::dyn_cast<UpdateExpressionNode>(expr))
return !upd->_prefix && _exprStartsWith(upd->_argument, expr, pred);
if (auto *un = llvh::dyn_cast<UnaryExpressionNode>(expr))
return !un->_prefix && _exprStartsWith(un->_argument, expr, pred);
if (auto *m = llvh::dyn_cast<MemberExpressionNode>(expr))
return _exprStartsWith(m->_object, expr, pred);
if (auto *t = llvh::dyn_cast<TaggedTemplateExpressionNode>(expr))
return _exprStartsWith(t->_tag, expr, pred);
return false;
}
bool GenJS::endsWithBlock(Node *node) {
// NOTE: I hope this function is converted into a loop.
if (!node)
return false;
if (llvh::isa<BlockStatementNode>(node))
return true;
if (llvh::isa<FunctionDeclarationNode>(node))
return true;
if (auto *s = llvh::dyn_cast<WhileStatementNode>(node))
return endsWithBlock(s->_body);
if (auto *s = llvh::dyn_cast<ForInStatementNode>(node))
return endsWithBlock(s->_body);
if (auto *s = llvh::dyn_cast<ForOfStatementNode>(node))
return endsWithBlock(s->_body);
if (auto *s = llvh::dyn_cast<ForStatementNode>(node))
return endsWithBlock(s->_body);
if (auto *s = llvh::dyn_cast<WithStatementNode>(node))
return endsWithBlock(s->_body);
if (llvh::isa<SwitchStatementNode>(node))
return true;
if (auto *s = llvh::dyn_cast<LabeledStatementNode>(node))
return endsWithBlock(s->_body);
if (auto *s = llvh::dyn_cast<TryStatementNode>(node))
return endsWithBlock(s->_finalizer ? s->_finalizer : s->_handler);
if (auto *s = llvh::dyn_cast<CatchClauseNode>(node))
return endsWithBlock(s->_body);
if (auto *s = llvh::dyn_cast<IfStatementNode>(node))
return endsWithBlock(s->_alternate ? s->_alternate : s->_consequent);
if (llvh::isa<ClassDeclarationNode>(node))
return true;
return false;
}
} // Anonymous namespace
void generateJS(llvh::raw_ostream &OS, Node *root, bool pretty) {
GenJS genJS{OS, pretty};
genJS.doIt(root);
}
} // namespace hermes