blob: f52370e497c8cc9ad816a55e1228f9bb2a5fbde5 [file] [log] [blame]
/*
* Copyright 2011 Google Inc.
*
* 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.gwt.dev.js;
import com.google.gwt.dev.jjs.HasSourceInfo;
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.js.ast.HasName;
import com.google.gwt.dev.js.ast.JsArrayAccess;
import com.google.gwt.dev.js.ast.JsArrayLiteral;
import com.google.gwt.dev.js.ast.JsBinaryOperation;
import com.google.gwt.dev.js.ast.JsBinaryOperator;
import com.google.gwt.dev.js.ast.JsBlock;
import com.google.gwt.dev.js.ast.JsBooleanLiteral;
import com.google.gwt.dev.js.ast.JsBreak;
import com.google.gwt.dev.js.ast.JsCase;
import com.google.gwt.dev.js.ast.JsCatch;
import com.google.gwt.dev.js.ast.JsConditional;
import com.google.gwt.dev.js.ast.JsContinue;
import com.google.gwt.dev.js.ast.JsDebugger;
import com.google.gwt.dev.js.ast.JsDefault;
import com.google.gwt.dev.js.ast.JsDoWhile;
import com.google.gwt.dev.js.ast.JsEmpty;
import com.google.gwt.dev.js.ast.JsExprStmt;
import com.google.gwt.dev.js.ast.JsExpression;
import com.google.gwt.dev.js.ast.JsFor;
import com.google.gwt.dev.js.ast.JsForIn;
import com.google.gwt.dev.js.ast.JsFunction;
import com.google.gwt.dev.js.ast.JsIf;
import com.google.gwt.dev.js.ast.JsInvocation;
import com.google.gwt.dev.js.ast.JsLabel;
import com.google.gwt.dev.js.ast.JsName;
import com.google.gwt.dev.js.ast.JsNameOf;
import com.google.gwt.dev.js.ast.JsNameRef;
import com.google.gwt.dev.js.ast.JsNew;
import com.google.gwt.dev.js.ast.JsNode;
import com.google.gwt.dev.js.ast.JsNullLiteral;
import com.google.gwt.dev.js.ast.JsNumberLiteral;
import com.google.gwt.dev.js.ast.JsNumericEntry;
import com.google.gwt.dev.js.ast.JsObjectLiteral;
import com.google.gwt.dev.js.ast.JsParameter;
import com.google.gwt.dev.js.ast.JsPostfixOperation;
import com.google.gwt.dev.js.ast.JsPrefixOperation;
import com.google.gwt.dev.js.ast.JsProgram;
import com.google.gwt.dev.js.ast.JsProgramFragment;
import com.google.gwt.dev.js.ast.JsPropertyInitializer;
import com.google.gwt.dev.js.ast.JsRegExp;
import com.google.gwt.dev.js.ast.JsReturn;
import com.google.gwt.dev.js.ast.JsSeedIdOf;
import com.google.gwt.dev.js.ast.JsStatement;
import com.google.gwt.dev.js.ast.JsStringLiteral;
import com.google.gwt.dev.js.ast.JsSwitch;
import com.google.gwt.dev.js.ast.JsSwitchMember;
import com.google.gwt.dev.js.ast.JsThisRef;
import com.google.gwt.dev.js.ast.JsThrow;
import com.google.gwt.dev.js.ast.JsTry;
import com.google.gwt.dev.js.ast.JsUnaryOperator;
import com.google.gwt.dev.js.ast.JsVars;
import com.google.gwt.dev.js.ast.JsVars.JsVar;
import com.google.gwt.dev.js.ast.JsWhile;
import com.google.gwt.dev.js.ast.NodeKind;
import com.google.gwt.thirdparty.guava.common.base.Preconditions;
import com.google.gwt.thirdparty.guava.common.collect.Sets;
import com.google.gwt.thirdparty.javascript.jscomp.AstValidator;
import com.google.gwt.thirdparty.javascript.rhino.IR;
import com.google.gwt.thirdparty.javascript.rhino.InputId;
import com.google.gwt.thirdparty.javascript.rhino.Node;
import com.google.gwt.thirdparty.javascript.rhino.Token;
import com.google.gwt.thirdparty.javascript.rhino.jstype.SimpleSourceFile;
import com.google.gwt.thirdparty.javascript.rhino.jstype.StaticSourceFile;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* Translate a GWT JS AST to a Closure Compiler AST.
*/
public class ClosureJsAstTranslator {
private static String getStringValue(double value) {
long longValue = (long) value;
// Return "1" instead of "1.0"
if (longValue == value) {
return Long.toString(longValue);
} else {
return Double.toString(value);
}
}
private final Map<String, StaticSourceFile> sourceCache = new HashMap<String, StaticSourceFile>();
private final boolean validate;
private final Set<String> globalVars = Sets.newHashSet();
private final Set<String> externalProperties = Sets.newHashSet();
private final Set<String> externalVars = Sets.newHashSet();
private final JsProgram program;
ClosureJsAstTranslator(boolean validate, JsProgram program) {
this.program = program;
this.validate = validate;
}
public Node translate(JsProgramFragment fragment, InputId inputId, String source) {
Node script = IR.script();
script.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true);
script.setInputId(inputId);
script.setStaticSourceFile(getClosureSourceFile(source));
for (JsStatement s : fragment.getGlobalBlock().getStatements()) {
script.addChildToBack(transform(s));
}
// Validate the structural integrity of the AST.
if (validate) {
new AstValidator().validateScript(script);
}
return script;
}
Set<String> getExternalPropertyReferences() {
return externalProperties;
}
Set<String> getExternalVariableReferences() {
return externalVars;
}
Set<String> getGlobalVariableNames() {
return globalVars;
}
private Node applyOriginalName(Node n, JsNode x) {
/*
* if (x instanceof HasSymbol) { Symbol symbol = ((HasSymbol)x).getSymbol(); if (symbol != null)
* { String originalName = symbol.getOriginalSymbolName(); n.putProp(Node.ORIGINALNAME_PROP,
* originalName); } }
*/
return n;
}
private Node applySourceInfo(Node n, HasSourceInfo srcNode) {
if (n != null && srcNode != null) {
SourceInfo info = srcNode.getSourceInfo();
if (info != null && info.getFileName() != null) {
n.setStaticSourceFile(getClosureSourceFile(info.getFileName()));
n.setLineno(info.getStartLine());
n.setCharno(0);
}
}
return n;
}
private StaticSourceFile getClosureSourceFile(String source) {
StaticSourceFile closureSourceFile = sourceCache.get(source);
if (closureSourceFile == null) {
closureSourceFile = new SimpleSourceFile(source, false);
sourceCache.put(source, closureSourceFile);
}
return closureSourceFile;
}
private String getName(JsName name) {
return name.getShortIdent();
}
private String getName(JsNameRef name) {
return name.getShortIdent();
}
private Node getNameNodeFor(HasName hasName) {
Node n = IR.name(getName(hasName.getName()));
applyOriginalName(n, (JsNode) hasName);
return applySourceInfo(n, (HasSourceInfo) hasName);
}
private int getTokenForOp(JsBinaryOperator op) {
switch (op) {
case MUL:
return Token.MUL;
case DIV:
return Token.DIV;
case MOD:
return Token.MOD;
case ADD:
return Token.ADD;
case SUB:
return Token.SUB;
case SHL:
return Token.LSH;
case SHR:
return Token.RSH;
case SHRU:
return Token.URSH;
case LT:
return Token.LT;
case LTE:
return Token.LE;
case GT:
return Token.GT;
case GTE:
return Token.GE;
case INSTANCEOF:
return Token.INSTANCEOF;
case INOP:
return Token.IN;
case EQ:
return Token.EQ;
case NEQ:
return Token.NE;
case REF_EQ:
return Token.SHEQ;
case REF_NEQ:
return Token.SHNE;
case BIT_AND:
return Token.BITAND;
case BIT_XOR:
return Token.BITXOR;
case BIT_OR:
return Token.BITOR;
case AND:
return Token.AND;
case OR:
return Token.OR;
case ASG:
return Token.ASSIGN;
case ASG_ADD:
return Token.ASSIGN_ADD;
case ASG_SUB:
return Token.ASSIGN_SUB;
case ASG_MUL:
return Token.ASSIGN_MUL;
case ASG_DIV:
return Token.ASSIGN_DIV;
case ASG_MOD:
return Token.ASSIGN_MOD;
case ASG_SHL:
return Token.ASSIGN_LSH;
case ASG_SHR:
return Token.ASSIGN_RSH;
case ASG_SHRU:
return Token.ASSIGN_URSH;
case ASG_BIT_AND:
return Token.ASSIGN_BITAND;
case ASG_BIT_OR:
return Token.ASSIGN_BITOR;
case ASG_BIT_XOR:
return Token.ASSIGN_BITXOR;
case COMMA:
return Token.COMMA;
}
return 0;
}
private int getTokenForOp(JsUnaryOperator op) {
switch (op) {
case BIT_NOT:
return Token.BITNOT;
case DEC:
return Token.DEC;
case DELETE:
return Token.DELPROP;
case INC:
return Token.INC;
case NEG:
return Token.NEG;
case POS:
return Token.POS;
case NOT:
return Token.NOT;
case TYPEOF:
return Token.TYPEOF;
case VOID:
return Token.VOID;
}
throw new IllegalStateException();
}
private Node transform(JsArrayAccess x) {
Node n = IR.getelem(transform(x.getArrayExpr()), transform(x.getIndexExpr()));
return applySourceInfo(n, x);
}
private Node transform(JsArrayLiteral x) {
Node n = IR.arraylit();
for (Object element : x.getExpressions()) {
JsExpression arg = (JsExpression) element;
n.addChildToBack(transform(arg));
}
return applySourceInfo(n, x);
}
private Node transform(JsBinaryOperation x) {
JsBinaryOperator op = x.getOperator();
Node n = new Node(getTokenForOp(op), transform(x.getArg1()), transform(x.getArg2()));
return applySourceInfo(n, x);
}
private Node transform(JsBlock x) {
Node n = IR.block();
for (JsStatement s : x.getStatements()) {
n.addChildToBack(transform(s));
}
return applySourceInfo(n, x);
}
private Node transform(JsBooleanLiteral x) {
Node n = x.getValue() ? IR.trueNode() : IR.falseNode();
return applySourceInfo(n, x);
}
private Node transform(JsBreak x) {
Node n;
JsNameRef label = x.getLabel();
if (label == null) {
n = IR.breakNode();
} else {
n = IR.breakNode(transformLabel(label));
}
return applySourceInfo(n, x);
}
private Node transform(JsCase x) {
Node expr = transform(x.getCaseExpr());
Node body = IR.block();
body.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true);
applySourceInfo(body, x);
for (Object element : x.getStmts()) {
JsStatement stmt = (JsStatement) element;
body.addChildToBack(transform(stmt));
}
Node n = IR.caseNode(expr, body);
return applySourceInfo(n, x);
}
private Node transform(JsCatch x) {
Node n = IR.catchNode(transformName(x.getParameter().getName()), transform(x.getBody()));
Preconditions.checkState(x.getCondition() == null);
return applySourceInfo(n, x);
}
private Node transform(JsConditional x) {
Node n =
IR.hook(transform(x.getTestExpression()), transform(x.getThenExpression()), transform(x
.getElseExpression()));
return applySourceInfo(n, x);
}
private Node transform(JsContinue x) {
Node n;
JsNameRef label = x.getLabel();
if (label == null) {
n = IR.continueNode();
} else {
n = IR.continueNode(transformLabel(label));
}
return applySourceInfo(n, x);
}
private Node transform(JsDebugger x) {
Node n = new Node(Token.DEBUGGER);
return applySourceInfo(n, x);
}
private Node transform(JsDefault x) {
Node body = IR.block();
body.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true);
applySourceInfo(body, x);
for (Object element : x.getStmts()) {
JsStatement stmt = (JsStatement) element;
body.addChildToBack(transform(stmt));
}
Node n = IR.defaultCase(body);
return applySourceInfo(n, x);
}
private Node transform(JsDoWhile x) {
Node n = IR.doNode(transformBody(x.getBody(), x), transform(x.getCondition()));
return applySourceInfo(n, x);
}
private Node transform(JsEmpty x) {
return IR.empty();
}
private Node transform(JsExpression x) {
assert x != null;
switch (x.getKind()) {
case ARRAY:
return transform((JsArrayLiteral) x);
case ARRAY_ACCESS:
return transform((JsArrayAccess) x);
case BINARY_OP:
return transform((JsBinaryOperation) x);
case CONDITIONAL:
return transform((JsConditional) x);
case INVOKE:
return transform((JsInvocation) x);
case FUNCTION:
return transform((JsFunction) x);
case OBJECT:
return transform((JsObjectLiteral) x);
case BOOLEAN:
return transform((JsBooleanLiteral) x);
case NULL:
return transform((JsNullLiteral) x);
case NUMBER:
if (x instanceof JsNumericEntry) {
return transform((JsNumericEntry) x);
}
return transform((JsNumberLiteral) x);
case REGEXP:
return transform((JsRegExp) x);
case STRING:
return transform((JsStringLiteral) x);
case THIS:
return transform((JsThisRef) x);
case NAME_OF:
return transform((JsNameOf) x);
case SEED_ID_OF:
return transform((JsSeedIdOf) x);
case NAME_REF:
return transform((JsNameRef) x);
case NEW:
return transform((JsNew) x);
case POSTFIX_OP:
return transform((JsPostfixOperation) x);
case PREFIX_OP:
return transform((JsPrefixOperation) x);
default:
throw new IllegalStateException("Unexpected expression type: "
+ x.getClass().getSimpleName());
}
}
private Node transform(JsExprStmt x) {
// The GWT JS AST doesn't produce function declarations, instead
// they are expressions statements:
Node expr = transform(x.getExpression());
if (!expr.isFunction()) {
return IR.exprResult(expr);
} else {
return expr;
}
}
private Node transform(JsFor x) {
// The init expressions or var decl.
//
Node init;
if (x.getInitExpr() != null) {
init = transform(x.getInitExpr());
} else if (x.getInitVars() != null) {
init = transform(x.getInitVars());
} else {
init = IR.empty();
}
// The loop test.
//
Node cond;
if (x.getCondition() != null) {
cond = transform(x.getCondition());
} else {
cond = IR.empty();
}
// The incr expression.
//
Node incr;
if (x.getIncrExpr() != null) {
incr = transform(x.getIncrExpr());
} else {
incr = IR.empty();
}
Node body = transformBody(x.getBody(), x);
Node n = IR.forNode(init, cond, incr, body);
return applySourceInfo(n, x);
}
private Node transform(JsForIn x) {
Node valueExpr;
if (x.getIterVarName() != null) {
valueExpr = new Node(Token.VAR, transformName(x.getIterVarName()));
} else {
// Just a name ref.
//
valueExpr = transform(x.getIterExpr());
}
Node n = IR.forIn(valueExpr, transform(x.getObjExpr()), transformBody(x.getBody(), x));
return applySourceInfo(n, x);
}
private Node transform(JsFunction x) {
Node name;
if (x.getName() != null) {
name = getNameNodeFor(x);
} else {
name = IR.name("");
}
applySourceInfo(name, x);
Node params = IR.paramList();
for (Object element : x.getParameters()) {
JsParameter param = (JsParameter) element;
params.addChildToBack(transform(param));
}
applySourceInfo(params, x);
Node n = IR.function(name, params, transform(x.getBody()));
if (name.getString().isEmpty()) {
n.putProp(Node.ORIGINALNAME_PROP, "");
} else {
applyOriginalName(n, x);
}
/*
* if (x.isConstructor()) { JSDocInfoBuilder builder = new JSDocInfoBuilder(false);
* builder.recordConstructor(); n.setJSDocInfo(builder.build(n)); }
*/
return applySourceInfo(n, x);
}
private Node transform(JsIf x) {
Node n = IR.ifNode(transform(x.getIfExpr()), transformBody(x.getThenStmt(), x));
if (x.getElseStmt() != null) {
n.addChildToBack(transformBody(x.getElseStmt(), x));
}
return applySourceInfo(n, x);
}
private Node transform(JsInvocation x) {
Node n = IR.call(transform(x.getQualifier()));
for (Object element : x.getArguments()) {
JsExpression arg = (JsExpression) element;
n.addChildToBack(transform(arg));
}
return applySourceInfo(n, x);
}
private Node transform(JsLabel x) {
Node n = IR.label(transformLabel(x.getName()), transform(x.getStmt()));
return applySourceInfo(n, x);
}
private Node transform(JsNameOf x) {
Node n = transformName(x.getName().getShortIdent(), x);
applyOriginalName(n, x);
return applySourceInfo(n, x);
}
private Node transform(JsNameRef x) {
Node n;
JsName name = x.getName();
boolean isExternal = name == null || !name.isObfuscatable();
if (x.getQualifier() != null) {
n = IR.getprop(transform(x.getQualifier()), transformNameAsString(x.getShortIdent(), x));
if (isExternal) {
this.externalProperties.add(x.getShortIdent());
}
} else {
n = transformName(x.getShortIdent(), x);
if (isExternal) {
this.externalVars.add(x.getShortIdent());
} else if (name.getEnclosing() == program.getScope()) {
this.globalVars.add(x.getShortIdent());
}
}
applyOriginalName(n, x);
return applySourceInfo(n, x);
}
private Node transform(JsNew x) {
Node n = IR.newNode(transform(x.getConstructorExpression()));
for (Object element : x.getArguments()) {
JsExpression arg = (JsExpression) element;
n.addChildToBack(transform(arg));
}
return applySourceInfo(n, x);
}
private Node transform(JsNullLiteral x) {
return IR.nullNode();
}
private Node transform(JsNumericEntry x) {
return IR.number(x.getValue());
}
private Node transform(JsNumberLiteral x) {
return IR.number(x.getValue());
}
private Node transform(JsObjectLiteral x) {
Node n = IR.objectlit();
for (Object element : x.getPropertyInitializers()) {
JsPropertyInitializer propInit = (JsPropertyInitializer) element;
Node key;
if (propInit.getLabelExpr().getKind() == NodeKind.NUMBER) {
key = transformNumberAsString((JsNumberLiteral) propInit.getLabelExpr());
key.putBooleanProp(Node.QUOTED_PROP, true);
} else if (propInit.getLabelExpr().getKind() == NodeKind.NAME_REF) {
key =
transformNameAsString(((JsNameRef) propInit.getLabelExpr()).getShortIdent(), propInit
.getLabelExpr());
} else {
key = transform(propInit.getLabelExpr());
}
Preconditions.checkState(key.isString(), key);
n.addChildToBack(IR.propdef(key, transform(propInit.getValueExpr())));
}
return applySourceInfo(n, x);
}
private Node transform(JsParameter x) {
return getNameNodeFor(x);
}
private Node transform(JsPostfixOperation x) {
Node n = new Node(getTokenForOp(x.getOperator()), transform(x.getArg()));
n.putBooleanProp(Node.INCRDECR_PROP, true);
return applySourceInfo(n, x);
}
private Node transform(JsPrefixOperation x) {
Node n = new Node(getTokenForOp(x.getOperator()), transform(x.getArg()));
return applySourceInfo(n, x);
}
private Node transform(JsRegExp x) {
String flags = x.getFlags();
Node n =
IR.regexp(Node.newString(x.getPattern()), Node.newString(flags != null ? x.getFlags() : ""));
return applySourceInfo(n, x);
}
private Node transform(JsReturn x) {
Node n = IR.returnNode();
JsExpression result = x.getExpr();
if (result != null) {
n.addChildToBack(transform(x.getExpr()));
}
return applySourceInfo(n, x);
}
private Node transform(JsSeedIdOf x) {
Node n = Node.newNumber(x.getSeedId());
return applySourceInfo(n, x);
}
private Node transform(JsStatement x) {
switch (x.getKind()) {
case BLOCK:
return transform((JsBlock) x);
case BREAK:
return transform((JsBreak) x);
case CONTINUE:
return transform((JsContinue) x);
case DEBUGGER:
return transform((JsDebugger) x);
case DO:
return transform((JsDoWhile) x);
case EMPTY:
return transform((JsEmpty) x);
case EXPR_STMT:
return transform((JsExprStmt) x);
case FOR:
return transform((JsFor) x);
case FOR_IN:
return transform((JsForIn) x);
case IF:
return transform((JsIf) x);
case LABEL:
return transform((JsLabel) x);
case RETURN:
return transform((JsReturn) x);
case SWITCH:
return transform((JsSwitch) x);
case THROW:
return transform((JsThrow) x);
case TRY:
return transform((JsTry) x);
case VARS:
return transform((JsVars) x);
case WHILE:
return transform((JsWhile) x);
default:
throw new IllegalStateException("Unexpected statement type: "
+ x.getClass().getSimpleName());
}
}
private Node transform(JsStringLiteral x) {
return IR.string(x.getValue());
}
private Node transform(JsSwitch x) {
Node n = IR.switchNode(transform(x.getExpr()));
for (JsSwitchMember member : x.getCases()) {
n.addChildToBack(transform(member));
}
return applySourceInfo(n, x);
}
private Node transform(JsSwitchMember x) {
switch (x.getKind()) {
case CASE:
return transform((JsCase) x);
case DEFAULT:
return transform((JsDefault) x);
default:
throw new IllegalStateException("Unexpected switch member type: "
+ x.getClass().getSimpleName());
}
}
private Node transform(JsThisRef x) {
Node n = new Node(Token.THIS);
return applySourceInfo(n, x);
}
private Node transform(JsThrow x) {
Node n = IR.throwNode(transform(x.getExpr()));
return applySourceInfo(n, x);
}
private Node transform(JsTry x) {
Node n = new Node(Token.TRY, transform(x.getTryBlock()));
Node catches = new Node(Token.BLOCK);
for (JsCatch catchBlock : x.getCatches()) {
catches.addChildToBack(transform(catchBlock));
}
n.addChildToBack(catches);
JsBlock finallyBlock = x.getFinallyBlock();
if (finallyBlock != null) {
n.addChildToBack(transform(finallyBlock));
}
return applySourceInfo(n, x);
}
private Node transform(JsVar x) {
Node n = getNameNodeFor(x);
JsExpression initExpr = x.getInitExpr();
if (initExpr != null) {
n.addChildToBack(transform(initExpr));
}
return applySourceInfo(n, x);
}
private Node transform(JsVars x) {
Node n = new Node(Token.VAR);
for (JsVar var : x) {
n.addChildToBack(transform(var));
}
return applySourceInfo(n, x);
}
private Node transform(JsWhile x) {
Node n =
IR.forNode(IR.empty(), transform(x.getCondition()), IR.empty(), transformBody(x.getBody(),
x));
return applySourceInfo(n, x);
}
private Node transformBody(JsStatement x, HasSourceInfo parent) {
Node n = transform(x);
if (!n.isBlock()) {
Node stmt = n;
n = IR.block();
if (!stmt.isEmpty()) {
n.addChildToBack(stmt);
}
applySourceInfo(n, parent);
}
return n;
}
private Node transformLabel(JsName label) {
Node n = IR.labelName(getName(label));
return applySourceInfo(n, label.getStaticRef());
}
private Node transformLabel(JsNameRef label) {
Node n = IR.labelName(getName(label));
return applySourceInfo(n, label);
}
private Node transformName(JsName name) {
Node n = IR.name(getName(name));
return applySourceInfo(n, name.getStaticRef());
}
private Node transformName(String name, HasSourceInfo info) {
Node n = IR.name(name);
return applySourceInfo(n, info);
}
private Node transformNameAsString(String name, HasSourceInfo info) {
Node n = IR.string(name);
return applySourceInfo(n, info);
}
private Node transformNumberAsString(JsNumberLiteral literalNode) {
Node irNode = Node.newString(getStringValue(literalNode.getValue()));
return irNode;
}
}