com.google.javascript.jscomp.jsonml.Writer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of closure-compiler Show documentation
Show all versions of closure-compiler Show documentation
Closure Compiler is a JavaScript optimizing compiler. It parses your
JavaScript, analyzes it, removes dead code and rewrites and minimizes
what's left. It also checks syntax, variable references, and types, and
warns about common JavaScript pitfalls. It is used in many of Google's
JavaScript apps, including Gmail, Google Web Search, Google Maps, and
Google Docs.
/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp.jsonml;
import com.google.common.base.Preconditions;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Iterator;
import java.util.Set;
/**
* Converts internal AST into JsonML tree.
*
* @author [email protected] (Daniel Hans)
*/
public class Writer {
/**
* Creates JsonML tree based on a specified AST.
* @param root AST node
* @return root of a created JsonML tree
*/
public JsonML processAst(Node root) {
Preconditions.checkNotNull(root);
Preconditions.checkArgument(
root.getType() == Token.BLOCK || root.getType() == Token.SCRIPT);
JsonML rootElement = new JsonML(TagType.BlockStmt);
if (root.getType() == Token.SCRIPT) {
processNode(root, rootElement);
return rootElement.getChild(0);
} else {
Node child = root.getFirstChild();
while (child != null) {
processNode(child, rootElement);
child = child.getNext();
}
// TODO(johnlenz): Add support for multiple scripts.
return rootElement.getChild(0);
}
}
/**
* Dispatches an AST node to a function which converts it to JsonML.
* @param node AST node to convert into JsonML element.
* @param currentParent element to which newly created JsonML element will
* be attached as a child
*/
private void processNode(Node node, JsonML currentParent) {
switch (node.getType()) {
case Token.RETURN:
processReturn(node, currentParent);
break;
case Token.BITOR:
processBinaryExpr(node, currentParent, "|");
break;
case Token.BITXOR:
processBinaryExpr(node, currentParent, "^");
break;
case Token.BITAND:
processBinaryExpr(node, currentParent, "&");
break;
case Token.EQ:
processBinaryExpr(node, currentParent, "==");
break;
case Token.NE:
processBinaryExpr(node, currentParent, "!=");
break;
case Token.LT:
processBinaryExpr(node, currentParent, "<");
break;
case Token.LE:
processBinaryExpr(node, currentParent, "<=");
break;
case Token.GT:
processBinaryExpr(node, currentParent, ">");
break;
case Token.GE:
processBinaryExpr(node, currentParent, ">=");
break;
case Token.LSH:
processBinaryExpr(node, currentParent, "<<");
break;
case Token.RSH:
processBinaryExpr(node, currentParent, ">>");
break;
case Token.URSH:
processBinaryExpr(node, currentParent, ">>>");
break;
case Token.ADD:
processBinaryExpr(node, currentParent, "+");
break;
case Token.SUB:
processBinaryExpr(node, currentParent, "-");
break;
case Token.MUL:
processBinaryExpr(node, currentParent, "*");
break;
case Token.DIV:
processBinaryExpr(node, currentParent, "/");
break;
case Token.MOD:
processBinaryExpr(node, currentParent, "%");
break;
case Token.NOT:
processUnaryExpr(node, currentParent, "!");
break;
case Token.BITNOT:
processUnaryExpr(node, currentParent, "~");
break;
case Token.POS:
processUnaryExpr(node, currentParent, "+");
break;
case Token.NEG:
processUnaryExpr(node, currentParent, "-");
break;
case Token.NEW:
processNew(node, currentParent, TagType.NewExpr);
break;
case Token.DELPROP:
processOneArgExpr(node, currentParent, TagType.DeleteExpr);
break;
case Token.TYPEOF:
processOneArgExpr(node, currentParent, TagType.TypeofExpr);
break;
case Token.GETPROP:
processMemberExpr(node, currentParent, ".");
break;
case Token.GETELEM:
processMemberExpr(node, currentParent, "[]");
break;
case Token.CALL:
processCall(node, currentParent);
break;
case Token.NAME:
processName(node, currentParent);
break;
case Token.NUMBER:
case Token.STRING:
case Token.NULL:
case Token.FALSE:
case Token.TRUE:
processLiteral(node, currentParent);
break;
case Token.THIS:
processThis(node, currentParent);
break;
case Token.SHEQ:
processBinaryExpr(node, currentParent, "===");
break;
case Token.SHNE:
processBinaryExpr(node, currentParent, "!==");
break;
case Token.REGEXP:
processRegExp(node, currentParent);
break;
case Token.THROW:
processThrow(node, currentParent);
break;
case Token.IN:
processBinaryExpr(node, currentParent, "in");
break;
case Token.INSTANCEOF:
processBinaryExpr(node, currentParent, "instanceof");
break;
case Token.ARRAYLIT:
processArrayLiteral(node, currentParent);
break;
case Token.OBJECTLIT:
processObjectLiteral(node, currentParent);
break;
case Token.TRY:
processTry(node, currentParent);
break;
case Token.COMMA:
processBinaryExpr(node, currentParent, ",");
break;
case Token.ASSIGN:
processAssignExpr(node, currentParent, "=");
break;
case Token.ASSIGN_BITOR:
processAssignExpr(node, currentParent, "|=");
break;
case Token.ASSIGN_BITXOR:
processAssignExpr(node, currentParent, "^=");
break;
case Token.ASSIGN_BITAND:
processAssignExpr(node, currentParent, "&=");
break;
case Token.ASSIGN_LSH:
processAssignExpr(node, currentParent, "<<=");
break;
case Token.ASSIGN_RSH:
processAssignExpr(node, currentParent, ">>=");
break;
case Token.ASSIGN_URSH:
processAssignExpr(node, currentParent, ">>>=");
break;
case Token.ASSIGN_ADD:
processAssignExpr(node, currentParent, "+=");
break;
case Token.ASSIGN_SUB:
processAssignExpr(node, currentParent, "-=");
break;
case Token.ASSIGN_MUL:
processAssignExpr(node, currentParent, "*=");
break;
case Token.ASSIGN_DIV:
processAssignExpr(node, currentParent, "/=");
break;
case Token.ASSIGN_MOD:
processAssignExpr(node, currentParent, "%=");
break;
case Token.HOOK:
processHook(node, currentParent);
break;
case Token.OR:
processLogicalExpr(node, currentParent, "||");
break;
case Token.AND:
processLogicalExpr(node, currentParent, "&&");
break;
case Token.INC:
processIncrDecrExpr(node, currentParent, "++");
break;
case Token.DEC:
processIncrDecrExpr(node, currentParent, "--");
break;
case Token.FUNCTION:
processFunction(node, currentParent);
break;
case Token.IF:
processIf(node, currentParent);
break;
case Token.SWITCH:
processSwitch(node, currentParent);
break;
case Token.CASE:
processCase(node, currentParent, TagType.Case);
break;
case Token.DEFAULT_CASE:
processCase(node, currentParent, TagType.DefaultCase);
break;
case Token.WHILE:
processLoop(node, currentParent, TagType.WhileStmt);
break;
case Token.DO:
processLoop(node, currentParent, TagType.DoWhileStmt);
break;
case Token.FOR:
processForLoop(node, currentParent);
break;
case Token.BREAK:
processJmp(node, currentParent, TagType.BreakStmt);
break;
case Token.CONTINUE:
processJmp(node, currentParent, TagType.ContinueStmt);
break;
case Token.VAR:
processVar(node, currentParent);
break;
case Token.WITH:
processWith(node, currentParent);
break;
case Token.CATCH:
processCatch(node, currentParent);
break;
case Token.VOID:
processUnaryExpr(node, currentParent, "void");
break;
case Token.EMPTY:
processEmpty(node, currentParent);
break;
case Token.BLOCK:
processBlock(node, currentParent);
break;
case Token.LABEL:
processLabel(node, currentParent);
break;
case Token.EXPR_RESULT:
processExprResult(node, currentParent);
break;
case Token.SCRIPT:
processScript(node, currentParent);
break;
}
}
private void processAssignExpr(Node node, JsonML currentParent, String op) {
processTwoArgExpr(node, currentParent, TagType.AssignExpr, op);
}
private void processArrayLiteral(Node node, JsonML currentParent) {
JsonML element = new JsonML(TagType.ArrayExpr);
currentParent.appendChild(element);
Iterator it = node.children().iterator();
while (it.hasNext()) {
processNode(it.next(), element);
}
}
private void processBinaryExpr(Node node, JsonML currentParent,
String op) {
processTwoArgExpr(node, currentParent, TagType.BinaryExpr, op);
}
private void processBlock(Node node, JsonML currentParent) {
JsonML element = new JsonML(TagType.BlockStmt);
if (currentParent != null) {
currentParent.appendChild(element);
}
processDirectives(node, element);
for (Node child : node.children()) {
processNode(child, element);
}
}
private void processCall(Node node, JsonML currentParent) {
Iterator it = node.children().iterator();
Node child = it.next();
JsonML element;
// the first child may indicate that it is invoke expression
// or a standard function call
switch (child.getType()) {
case Token.GETPROP: // a.x()
case Token.GETELEM: // a[x]()
// we have to process this node here and cannot call processNode(child)
// other children of CALL represent arguments, so we need to have
// access to them while processing InvokeExpr
element = new JsonML(TagType.InvokeExpr);
element.setAttribute(
TagAttr.OP,
child.getType() == Token.GETPROP ? "." : "[]");
currentParent.appendChild(element);
// there should be exactly two children
Node grandchild = child.getFirstChild();
processNode(grandchild, element);
processNode(grandchild.getNext(), element);
break;
case Token.NAME:
// caja treats calls to eval in a special way
if (child.getString().equals("eval")) {
element = new JsonML(TagType.EvalExpr);
} else {
// element representing function name is created
element = new JsonML(TagType.IdExpr);
element.setAttribute(TagAttr.NAME, child.getString());
// element representing function is created
element = new JsonML(TagType.CallExpr, element);
}
currentParent.appendChild(element);
break;
default:
// it addresses all cases where the first argument evaluates to
// another expression
element = new JsonML(TagType.CallExpr);
currentParent.appendChild(element);
processNode(child, element);
break;
}
// there may be arguments applied
while (it.hasNext()) {
processNode(it.next(), element);
}
}
private void processCase(Node node, JsonML currentParent,
TagType type) {
JsonML element = new JsonML(type);
currentParent.appendChild(element);
Node child = node.getFirstChild();
// for case, the first child represents its argument
if (type == TagType.Case) {
processNode(child, element);
child = child.getNext();
}
// it should be a BLOCK which is required by rhino for compatibility
// the writer skips the node and move on to its children
Preconditions.checkNotNull(child);
Preconditions.checkState(child.getType() == Token.BLOCK);
child = child.getFirstChild();
while (child != null) {
processNode(child, element);
child = child.getNext();
}
}
private void processCatch(Node node, JsonML currentParent) {
JsonML element = new JsonML(TagType.CatchClause);
currentParent.appendChild(element);
// the first child represents exception's name
Node child = node.getFirstChild();
JsonML patt = new JsonML(TagType.IdPatt);
patt.setAttribute(TagAttr.NAME, child.getString());
element.appendChild(patt);
// the second child represents content
child = child.getNext();
processNode(child, element);
}
private void processEmpty(Node node, JsonML currentParent) {
if (currentParent.getType() == TagType.ArrayExpr) {
// Empty expression are only found in Array literals
currentParent.appendChild(new JsonML(TagType.Empty));
} else {
currentParent.appendChild(new JsonML(TagType.EmptyStmt));
}
}
private void processExprResult(Node node, JsonML currentParent) {
// this not interesting to JsonML, so we just need to skip it
processNode(node.getFirstChild(), currentParent);
}
private void processForLoop(Node node, JsonML currentParent) {
if (NodeUtil.isForIn(node)) {
processLoop(node, currentParent, TagType.ForInStmt);
} else {
processLoop(node, currentParent, TagType.ForStmt);
}
}
private void processFunction(Node node, JsonML currentParent) {
JsonML element;
if (NodeUtil.isFunctionDeclaration(node)) {
element = new JsonML(TagType.FunctionDecl);
} else { // isFunctionExpresion == true
element = new JsonML(TagType.FunctionExpr);
}
currentParent.appendChild(element);
// the first child represents function's name
Node child = node.getFirstChild();
String name = child.getString();
if (!name.equals("")) {
JsonML nameElement = new JsonML(TagType.IdPatt);
nameElement.setAttribute(TagAttr.NAME, name);
element.appendChild(nameElement);
} else {
element.appendChild(new JsonML(TagType.Empty));
}
// the second child is a wrapper for formal parameters
child = child.getNext();
JsonML params = new JsonML(TagType.ParamDecl);
element.appendChild(params);
Iterator it = child.children().iterator();
while (it.hasNext()) {
JsonML param = new JsonML(TagType.IdPatt);
Node nameNode = it.next();
param.setAttribute(TagAttr.NAME, nameNode.getString());
params.appendChild(param);
}
// the third child represents function's body
child = child.getNext();
// it can contain some directives
processDirectives(child, element);
it = child.children().iterator();
while (it.hasNext()) {
processNode(it.next(), element);
}
}
private void processHook(Node node, JsonML currentParent) {
JsonML element = new JsonML(TagType.ConditionalExpr);
currentParent.appendChild(element);
processChildren(node, element);
}
private void processIf(Node node, JsonML currentParent) {
JsonML element = new JsonML(TagType.IfStmt);
currentParent.appendChild(element);
Iterator it = node.children().iterator();
// there should be at least one child
while (it.hasNext()) {
processNode(it.next(), element);
}
// append EmptyStmt for each missing part
int childCount = node.getChildCount();
Preconditions.checkState(childCount >= 2);
if (childCount < 3) { // no "else" part for sure
element.appendChild(new JsonML(TagType.EmptyStmt));
}
}
private void processIncrDecrExpr(Node node, JsonML currentParent,
String op) {
JsonML element = new JsonML(TagType.CountExpr);
currentParent.appendChild(element);
if (op.equals("++")) {
element.setAttribute(TagAttr.OP, "++");
} else { // op.euals("--")
element.setAttribute(TagAttr.OP, "--");
}
if (node.getIntProp(Node.INCRDECR_PROP) == 1) {
element.setAttribute(TagAttr.IS_PREFIX, false);
} else { // INCRDECR_PROP == 0
element.setAttribute(TagAttr.IS_PREFIX, true);
}
// there is exactly one child
processNode(node.getFirstChild(), element);
}
private void processJmp(Node node, JsonML currentParent,
TagType type) {
JsonML element = new JsonML(type);
currentParent.appendChild(element);
// optional child may point to a label
Node child = node.getFirstChild();
if (child != null) {
element.setAttribute(TagAttr.LABEL, child.getString());
}
}
private void processLabel(Node node, JsonML currentParent) {
JsonML element = new JsonML(TagType.LabelledStmt);
currentParent.appendChild(element);
// the first child represents label's name
Node child = node.getFirstChild();
element.setAttribute(TagAttr.LABEL, child.getString());
// the second child represents labelled content
child = child.getNext();
processNode(child, element);
}
private void processLiteral(Node node, JsonML currentParent) {
JsonML element = new JsonML(TagType.LiteralExpr);
switch (node.getType()) {
case Token.NUMBER:
element.setAttribute(TagAttr.TYPE, "number");
element.setAttribute(TagAttr.VALUE, node.getDouble());
break;
case Token.STRING:
element.setAttribute(TagAttr.TYPE, "string");
element.setAttribute(TagAttr.VALUE, node.getString());
break;
case Token.NULL:
element.setAttribute(TagAttr.TYPE, "null");
element.setAttribute(TagAttr.VALUE, null);
break;
case Token.TRUE:
element.setAttribute(TagAttr.TYPE, "boolean");
element.setAttribute(TagAttr.VALUE, true);
break;
case Token.FALSE:
element.setAttribute(TagAttr.TYPE, "boolean");
element.setAttribute(TagAttr.VALUE, false);
break;
default:
throw new IllegalArgumentException("Illegal type of node.");
}
currentParent.appendChild(element);
}
private void processLogicalExpr(Node node, JsonML currentParent,
String op) {
if (op.equals("||")) {
processTwoArgExpr(node, currentParent, TagType.LogicalOrExpr);
} else if (op.endsWith("&&")) {
processTwoArgExpr(node, currentParent, TagType.LogicalAndExpr);
} else {
throw new IllegalArgumentException("Unsupported value of op argument.");
}
}
private void processLoop(Node node, JsonML currentParent,
TagType type) {
JsonML element = new JsonML(type);
currentParent.appendChild(element);
processChildren(node, element);
}
private void processMemberExpr(Node node, JsonML currentParent,
String op) {
JsonML element = new JsonML(TagType.MemberExpr);
element.setAttribute(TagAttr.OP, op);
currentParent.appendChild(element);
// there should be exactly two children
Node child = node.getFirstChild();
processNode(child, element);
processNode(child.getNext(), element);
}
private void processName(Node node, JsonML currentParent) {
Preconditions.checkState(!node.hasChildren());
JsonML element = new JsonML(TagType.IdExpr);
element.setAttribute(TagAttr.NAME, node.getString());
currentParent.appendChild(element);
}
private void processNew(Node node, JsonML currentParent, TagType type) {
JsonML element = new JsonML(type);
currentParent.appendChild(element);
processChildren(node, element);
}
private void processObjectLiteral(Node node, JsonML currentParent) {
JsonML element = new JsonML(TagType.ObjectExpr);
currentParent.appendChild(element);
for (Node key : node.children()) {
Node value = key.getFirstChild();
JsonML item;
Object name;
switch (key.getType()) {
case Token.STRING:
item = new JsonML(TagType.DataProp);
name = key.getString();
break;
case Token.NUMBER:
item = new JsonML(TagType.DataProp);
name = key.getDouble();
break;
case Token.GETTER_DEF:
item = new JsonML(TagType.GetterProp);
name = key.getString();
break;
case Token.SETTER_DEF:
item = new JsonML(TagType.SetterProp);
name = key.getString();
break;
default:
throw new IllegalArgumentException("Illegal type of node.");
}
item.setAttribute(TagAttr.NAME, name);
processNode(value, item);
element.appendChild(item);
}
}
private void processRegExp(Node node, JsonML currentParent) {
JsonML element = new JsonML(TagType.RegExpExpr);
currentParent.appendChild(element);
// first child represents expression's body
Node child = node.getFirstChild();
element.setAttribute(TagAttr.BODY, child.getString());
// optional second child represents flags
String flags = "";
child = child.getNext();
if (child != null) {
flags = child.getString();
}
element.setAttribute(TagAttr.FLAGS, flags);
}
private void processSwitch(Node node, JsonML currentParent) {
JsonML element = new JsonML(TagType.SwitchStmt);
currentParent.appendChild(element);
// the first child represents expression
Node child = node.getFirstChild();
processNode(child, element);
// next children represent particular cases
for (Node c = child.getNext(); c != null; c = c.getNext()) {
processNode(c, element);
}
}
private void processThis(Node node, JsonML currentParent) {
currentParent.appendChild(new JsonML(TagType.ThisExpr));
}
private void processThrow(Node node, JsonML currentParent) {
JsonML element = new JsonML(TagType.ThrowStmt);
currentParent.appendChild(element);
// there is exactly one child
processNode(node.getFirstChild(), element);
}
private void processTry(Node node, JsonML currentParent) {
JsonML element = new JsonML(TagType.TryStmt);
currentParent.appendChild(element);
// first child represents actual try block
Node child = node.getFirstChild();
processNode(child, element);
// second child (precisely: child of that child) represents catch block
child = child.getNext();
if (child.hasChildren()) {
processNode(child.getFirstChild(), element);
} else { // catch block is not present
element.appendChild(new JsonML(TagType.Empty));
}
//optional third child represents finally block
child = child.getNext();
if (child != null) {
processNode(child, element);
}
}
private void processTwoArgExpr(Node node, JsonML currentParent,
TagType type) {
processTwoArgExpr(node, currentParent, type, null);
}
private void processTwoArgExpr(Node node, JsonML currentParent,
TagType type, String op) {
JsonML element = new JsonML(type);
if (op != null) {
element.setAttribute(TagAttr.OP, op);
}
currentParent.appendChild(element);
Preconditions.checkState(node.getChildCount() == 2);
Node child = node.getFirstChild();
processNode(child, element);
processNode(child.getNext(), element);
}
/**
* Process nodes which JsonML represents by UnaryExpr.
* @param node node to process
* @param op operation for this unary expression - depends on node type
*/
private void processUnaryExpr(Node node, JsonML currentParent,
String op) {
JsonML element = new JsonML(TagType.UnaryExpr);
element.setAttribute(TagAttr.OP, op);
currentParent.appendChild(element);
processNode(node.getFirstChild(), element);
}
private void processVar(Node node, JsonML currentParent) {
JsonML element = new JsonML(TagType.VarDecl);
currentParent.appendChild(element);
//there may be many actual declarations
Iterator it = node.children().iterator();
while (it.hasNext()) {
Node child = it.next(); // this node represents var's id
// its own child represents initial value
JsonML id = new JsonML(TagType.IdPatt);
id.setAttribute(TagAttr.NAME, child.getString());
if (child.hasChildren()) {
JsonML patt = new JsonML(TagType.InitPatt);
element.appendChild(patt);
patt.appendChild(id);
processNode(child.getFirstChild(), patt);
} else {
element.appendChild(id);
}
}
}
private void processReturn(Node currentNode, JsonML currentParent) {
JsonML element = new JsonML(TagType.ReturnStmt);
currentParent.appendChild(element);
// there is exactly one child if return statement is not empty
if (currentNode.hasChildren()) {
processNode(currentNode.getFirstChild(), element);
}
}
private void processScript(Node node, JsonML currentParent) {
JsonML element = new JsonML(TagType.Program);
currentParent.appendChild(element);
processDirectives(node, element);
processChildren(node, element);
}
private void processWith(Node node, JsonML currentParent) {
JsonML element = new JsonML(TagType.WithStmt);
currentParent.appendChild(element);
// the first child represent object
Node child = node.getFirstChild();
processNode(child, element);
// the second one represents content
child = child.getNext();
processNode(child, element);
}
private void processChildren(Node node, JsonML currentParent) {
for (Node child : node.children()) {
processNode(child, currentParent);
}
}
private void processDirectives(Node node, JsonML currectParent) {
Set directives = node.getDirectives();
if (directives == null) {
return;
}
for (String directive : directives) {
JsonML element = new JsonML(TagType.PrologueDecl);
element.setAttribute(TagAttr.DIRECTIVE, directive);
element.setAttribute(TagAttr.VALUE, directive);
currectParent.appendChild(element);
}
}
private void processOneArgExpr(Node node, JsonML currentParent,
TagType type) {
JsonML element = new JsonML(type);
currentParent.appendChild(element);
// there is only one child node
processNode(node.getFirstChild(), element);
}
}