Package com.google.caja.parser.quasiliteral

Source Code of com.google.caja.parser.quasiliteral.ES53Rewriter

// Copyright (C) 2007 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.caja.parser.quasiliteral;

import static com.google.caja.parser.js.SyntheticNodes.s;

import com.google.caja.lexer.FilePosition;
import com.google.caja.parser.ParseTreeNode;
import com.google.caja.parser.ParseTreeNodeContainer;
import com.google.caja.parser.js.ArrayConstructor;
import com.google.caja.parser.js.AssignOperation;
import com.google.caja.parser.js.Block;
import com.google.caja.parser.js.BreakStmt;
import com.google.caja.parser.js.CajoledModule;
import com.google.caja.parser.js.CaseStmt;
import com.google.caja.parser.js.Conditional;
import com.google.caja.parser.js.ContinueStmt;
import com.google.caja.parser.js.ControlOperation;
import com.google.caja.parser.js.DebuggerStmt;
import com.google.caja.parser.js.Declaration;
import com.google.caja.parser.js.DefaultCaseStmt;
import com.google.caja.parser.js.DirectivePrologue;
import com.google.caja.parser.js.Elision;
import com.google.caja.parser.js.Expression;
import com.google.caja.parser.js.ExpressionStmt;
import com.google.caja.parser.js.FormalParam;
import com.google.caja.parser.js.FunctionConstructor;
import com.google.caja.parser.js.FunctionDeclaration;
import com.google.caja.parser.js.GetterProperty;
import com.google.caja.parser.js.Identifier;
import com.google.caja.parser.js.IntegerLiteral;
import com.google.caja.parser.js.LabeledStatement;
import com.google.caja.parser.js.LabeledStmtWrapper;
import com.google.caja.parser.js.Literal;
import com.google.caja.parser.js.Loop;
import com.google.caja.parser.js.MultiDeclaration;
import com.google.caja.parser.js.Noop;
import com.google.caja.parser.js.NumberLiteral;
import com.google.caja.parser.js.ObjProperty;
import com.google.caja.parser.js.ObjectConstructor;
import com.google.caja.parser.js.Operation;
import com.google.caja.parser.js.Operator;
import com.google.caja.parser.js.Reference;
import com.google.caja.parser.js.RegexpLiteral;
import com.google.caja.parser.js.ReturnStmt;
import com.google.caja.parser.js.SimpleOperation;
import com.google.caja.parser.js.Statement;
import com.google.caja.parser.js.StringLiteral;
import com.google.caja.parser.js.SwitchStmt;
import com.google.caja.parser.js.ThrowStmt;
import com.google.caja.parser.js.TranslatedCode;
import com.google.caja.parser.js.TryStmt;
import com.google.caja.parser.js.UncajoledModule;
import com.google.caja.parser.js.ValueProperty;
import com.google.caja.reporting.BuildInfo;
import com.google.caja.reporting.MessageLevel;
import com.google.caja.reporting.MessagePart;
import com.google.caja.reporting.MessageQueue;
import com.google.caja.util.Lists;
import com.google.caja.util.Sets;

import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* Rewrites a JavaScript parse tree to comply with default Caja rules.
*
* <p>By design, a rewriter is a "one-shot" object to be used for rewriting
* one module then discarded.
*
* @author ihab.awad@gmail.com (Ihab Awad)
*/
@RulesetDescription(
    name="ES5/3 Transformation Rules",
    synopsis="Default set of transformations used by ES5/3"
  )
public class ES53Rewriter extends Rewriter {
  private final BuildInfo buildInfo;
  private final Set<StringLiteral> includedModules = Sets.newTreeSet(
      new Comparator<StringLiteral>() {
    @Override
    public int compare(StringLiteral o1, StringLiteral o2) {
      return o1.getUnquotedValue().compareTo(o2.getUnquotedValue());
    }
  });
  private final Set<StringLiteral> inlinedModules = Sets.newTreeSet(
      new Comparator<StringLiteral>() {
    @Override
    public int compare(StringLiteral o1, StringLiteral o2) {
      return o1.getUnquotedValue().compareTo(o2.getUnquotedValue());
    }
  });

  /** Index of the last node that wasn't translated from another language. */
  private int lastRealJavascriptChild(
      List<? extends ParseTreeNode> nodes) {
    int lasti = nodes.size();
    while (--lasti >= 0) {
      if (!isForSideEffect(nodes.get(lasti))) {
        break;
      }
    }
    return lasti;
  }

  /**
   * Generate the header that should be placed at the beginning of the body
   * of the translation of an ES5/3 function body.
   *
   * @param scope The scope that results from expanding (cajoling) the ES5/3
   *              function body.
   * @return If the function body contains a free use of <tt>arguments</tt>,
   *         translate to an initialization of cajoled arguments based on
   *         an entry snapshot of the real ones. If the function body
   *         contains a free use of <tt>this</tt>, translate to an
   *         initialization of <tt>dis___</tt> to a sanitized this, by
   *         replacing the global object with <tt>void 0</tt>.
   */
  public static ParseTreeNode getFunctionHeadDeclarations(Scope scope) {
    List<ParseTreeNode> stmts = Lists.newArrayList();

    if (scope.hasFreeArguments()) {
      stmts.add(QuasiBuilder.substV(
          "___.deodorize(@ga, -6);" +
          "var @la = ___.args(@ga);",
          "la", s(new Identifier(
              FilePosition.UNKNOWN, ReservedNames.LOCAL_ARGUMENTS)),
          "ga", Rule.newReference(FilePosition.UNKNOWN,
                                  ReservedNames.ARGUMENTS)));
    }
    if (scope.hasFreeThis()) {
      stmts.add(QuasiBuilder.substV(
          "var dis___ = (this && this.___) ? void 0 : this;"));
    }
    return new ParseTreeNodeContainer(stmts);
  }

  /**
   * Find the last expression statement executed in a block of code and
   * emit its value to a variable "moduleResult___" so that it can used as
   * the result of module loading.
   */
  @SuppressWarnings("unchecked")
  public ParseTreeNode returnLast(ParseTreeNode node) {
    ParseTreeNode result = null;
    // Code translated from another language should not be used as the module
    // result.
    if (isForSideEffect(node)) { return node; }
    if (node instanceof ExpressionStmt) {
      result = new ExpressionStmt(
          node.getFilePosition(),
          (Expression) QuasiBuilder.substV(
              "moduleResult___ = @result;",
              "result", ((ExpressionStmt) node).getExpression()));
    } else if (node instanceof ParseTreeNodeContainer) {
      List<ParseTreeNode> nodes = Lists.newArrayList(node.children());
      int lasti = lastRealJavascriptChild(nodes);
      if (lasti >= 0) {
        nodes.set(lasti, returnLast(nodes.get(lasti)));
        result = new ParseTreeNodeContainer(nodes);
      }
    } else if (node instanceof Block) {
      List<Statement> stats = Lists.newArrayList();
      stats.addAll((Collection<? extends Statement>) node.children());
      int lasti = lastRealJavascriptChild(stats);
      if (lasti >= 0) {
        stats.set(lasti, (Statement) returnLast(stats.get(lasti)));
        result = new Block(node.getFilePosition(), stats);
      }
    } else if (node instanceof Conditional) {
      List<ParseTreeNode> nodes = Lists.newArrayList();
      nodes.addAll(node.children());
      int lasti = nodes.size() - 1;
      for (int i = 1; i <= lasti; i += 2) {  // Even are conditions.
        nodes.set(i, returnLast(nodes.get(i)));
      }
      if ((lasti & 1) == 0) {  // else clause
        nodes.set(lasti, returnLast(nodes.get(lasti)));
      }
      result = new Conditional(node.getFilePosition(), null, nodes);
    } else if (node instanceof TryStmt) {
      TryStmt tryer = (TryStmt) node;
      result = new TryStmt(
          node.getFilePosition(),
          (Block) returnLast(tryer.getBody()),
          tryer.getCatchClause(),
          tryer.getFinallyClause());
    }
    if (null == result) { return node; }
    result.getAttributes().putAll(node.getAttributes());
    return result;
  }

  private static final FilePosition UNK = FilePosition.UNKNOWN;

  // A NOTE ABOUT MATCHING MEMBER ACCESS EXPRESSIONS
  // When we match the pattern like '@x.@y' or '@x.@y()' against a specimen,
  // the result is that 'y' is bound to the rightmost component, and 'x' is
  // the remaining sub-expression on the left. Thus the result of matching
  //     @x.@y, @x.@y(), @x.@y(arg), @x.@y(args*), ...
  // is that 'y' is always bound to a Reference.

  private final Rule[] cajaRules = {

    ////////////////////////////////////////////////////////////////////////
    // Do nothing if the node is already the result of some translation
    ////////////////////////////////////////////////////////////////////////

    // See also rules in SyntheticRuleSet.

    new Rule() {
      @Override
      @RuleDescription(
          name="translatedCode",
          synopsis="Allow code received from a *->JS translator",
          reason="Translated code should not be treated as user supplied JS.",
          matches="<TranslatedCode>",
          matchNode=TranslatedCode.class)
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        if (node instanceof TranslatedCode) {
          Statement rewritten
              = ((TranslatedCode) expandAll(node, scope)).getTranslation();
          markTreeForSideEffect(rewritten);
          return rewritten;
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="labeledStatement",
          synopsis="Statically reject if a label with `__` suffix is found",
          reason="Caja reserves the `__` suffix for internal use",
          matches="@lbl: @stmt;",
          substitutes="@lbl: @stmt;")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        if (node instanceof LabeledStatement) {
          String label = ((LabeledStatement) node).getLabel();
          if (label.endsWith("__")) {
            mq.addMessage(
                RewriterMessageType.LABELS_CANNOT_END_IN_DOUBLE_UNDERSCORE,
                node.getFilePosition(),
                MessagePart.Factory.valueOf(label));
          }
        }
        return NONE;
      }
    },

    ////////////////////////////////////////////////////////////////////////
    // Module envelope
    ////////////////////////////////////////////////////////////////////////

    new Rule() {
      @Override
      @RuleDescription(
          name="moduleEnvelope",
          synopsis="Cajole an UncajoledModule into a CajoledModule. Note "
              + "that the ouptut is a CajoledModule wrapper *around* the"
              + "contents of the 'substitutes' of this rule.",
          reason="So that the module loader can be invoked to load a module.",
          matches="<an UncajoledModule>",
          matchNode=UncajoledModule.class,
          substitutes=(
              ""
              + "(/*@synthetic*/{"
              + "  instantiate: /*@synthetic*/function (___, IMPORTS___) {"
              + "    /*var moduleResult___ = ___.NO_RESULT;*/"
              + "    @rewrittenModuleStmts*;"
              + "    /*return moduleResult___;*/"
              + "  },"
              + "  @metaKeys*: @metaValues*,"
              + "  cajolerName: @cajolerName,"
              + "  cajolerVersion: @cajolerVersion,"
              + "  cajoledDate: @cajoledDate"
              + "})"))
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        if (node instanceof UncajoledModule) {
          Statement returnStmt = (Statement) QuasiBuilder.substV(
              "return moduleResult___;");
          markTreeForSideEffect(returnStmt);
          Block inputModuleStmts = (Block) QuasiBuilder.substV(
              ""
              + "var moduleResult___ = ___./*@synthetic*/NO_RESULT;"
              + "@moduleBody*;"
              + "@returnStmt",
              "moduleBody", new ParseTreeNodeContainer(
                  ((UncajoledModule) node).getModuleBody().children()),
              "returnStmt", returnStmt);
          Block rewrittenModuleStmts = (Block) expand(inputModuleStmts, null);
          ParseTreeNodeContainer metaKeys = new ParseTreeNodeContainer();
          ParseTreeNodeContainer metaValues = new ParseTreeNodeContainer();
          if (!includedModules.isEmpty()) {
            metaKeys.appendChild(StringLiteral.valueOf(UNK, "includedModules"));
            metaValues.appendChild(
                new ArrayConstructor(UNK, Lists.newArrayList(includedModules)));
          }
          if (!inlinedModules.isEmpty()) {
            metaKeys.appendChild(StringLiteral.valueOf(UNK, "inlinedModules"));
            metaValues.appendChild(
                new ArrayConstructor(UNK, Lists.newArrayList(inlinedModules)));
          }

          ObjectConstructor moduleObjectLiteral = (ObjectConstructor) substV(
              "rewrittenModuleStmts", returnLast(rewrittenModuleStmts),
              "metaKeys", metaKeys,
              "metaValues", metaValues,
              "cajolerName", new StringLiteral(UNK, "com.google.caja"),
              "cajolerVersion", new StringLiteral(
                  UNK, buildInfo.getBuildVersion()),
              "cajoledDate", new IntegerLiteral(
                  UNK, buildInfo.getCurrentTime()));
          return new CajoledModule(moduleObjectLiteral);
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="module",
          synopsis="Return last expr-statement",
          reason="Builds the module body encapsulation around the ES5/3 "
              + "code block.",
          matches="{@ss*;}",
          substitutes="var dis___ = IMPORTS___; @startStmts*; @expanded*;")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        if (node instanceof Block && scope == null) {
          Scope s2 = Scope.fromProgram((Block) node, ES53Rewriter.this);
          List<ParseTreeNode> expanded = Lists.newArrayList();
          for (ParseTreeNode c : node.children()) {
            ParseTreeNode expandedC = expand(c, s2);
            if (expandedC instanceof Noop) { continue; }
            expanded.add(expandedC);
          }
          return substV(
              "startStmts", new ParseTreeNodeContainer(s2.getStartStatements()),
              "expanded", new ParseTreeNodeContainer(expanded));
        }
        return NONE;
      }
    },

    ////////////////////////////////////////////////////////////////////////
    // Support hoisting of functions to the top of their containing block
    ////////////////////////////////////////////////////////////////////////

    // TODO: (metaweta) Implement block scoping.
    new Rule() {
      @Override
      @RuleDescription(
          name="block",
          synopsis="Initialize named functions at the beginning of their "
              + "enclosing block.",
          reason="Nested named function declarations are illegal in ES3 but are "
              + "universally supported by all JavaScript implementations, "
              + "though in different ways. The compromise semantics currently "
              + "supported by ES5/3 is to hoist the declaration of a variable "
              + "with the function's name to the beginning of the enclosing "
              + "function body or module top level, and to initialize this "
              + "variable to a new anonymous function every time control "
              + "re-enters the enclosing block."
              + "\n"
              + "Note that ES-Harmony will specify a better and safer semantics "
              + "-- block level lexical scoping -- that we'd like to adopt into "
              + "ES5/3 eventually. However, it is so challenging to implement "
              + "this semantics by translation to currently-implemented "
              + "JavaScript that we provide something quicker and dirtier "
              + "for now.",
          matches="{@ss*;}",
          substitutes="@startStmts*; @ss*;")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        if (node instanceof Block) {
          List<Statement> expanded = Lists.newArrayList();
          Scope s2 = Scope.fromPlainBlock(scope);
          for (Statement c : ((Block) node).children()) {
            ParseTreeNode rewritten = expand(c, s2);
            if (rewritten.getClass() == Block.class) {
              expanded.addAll(((Block) rewritten).children());
            } else if (!(rewritten instanceof Noop)) {
              expanded.add((Statement) rewritten);
            }
          }
          return substV(
              "startStmts", new ParseTreeNodeContainer(s2.getStartStatements()),
              "ss", new ParseTreeNodeContainer(expanded));
        }
        return NONE;
      }
    },

    ////////////////////////////////////////////////////////////////////////
    // with - disallow the 'with' construct
    ////////////////////////////////////////////////////////////////////////

    new Rule() {
      @Override
      @RuleDescription(
          name="with",
          synopsis="Statically reject if a `with` block is found.",
          reason="`with` violates the assumptions made by Scope, and makes it "
              + "very hard to write a Scope that works. "
              + "http://yuiblog.com/blog/2006/04/11/with-statement-considered-harmful/ "
              + "briefly touches on why `with` is bad for programmers. For "
              + "reviewers: matching of references with declarations can only "
              + "be done at runtime. All other secure JS subsets that we know "
              + "of (ADSafe, Jacaranda, & FBJS) also disallow `with`.",
          matches="with (@scope) @body;",
          substitutes="<reject>")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = this.match(node);
        if (bindings != null) {
          mq.addMessage(
              RewriterMessageType.WITH_BLOCKS_NOT_ALLOWED,
              node.getFilePosition());
          return node;
        }
        return NONE;
      }
    },

    ////////////////////////////////////////////////////////////////////////
    // foreach - "for ... in" loops
    ////////////////////////////////////////////////////////////////////////

    new Rule () {
      @Override
      @RuleDescription(
          name="foreachExpr",
          synopsis="Filter nonenumerable keys.",
          reason="",
          matches="for (@k in @o) @ss;",
          substitutes=(
              "@ot = Object(@o).e___();" +
              "for (@kts in @ot) {" +
              "  if (typeof @kt === 'number' || ('' + (+@kt)) === @kt) {" +
              "    @assign1; /* k = kt; */" +
              "  } else {" +
              "    if (/^NUM___/.test(@kt) && /__$/.test(@kt)) { continue; }" +
              "    @m = @kt.match(/([\\s\\S]*)_e___$/);" +
              "    if (!@m || !@ot[@kt]) { continue; }" +
              "    @assign2; /* k = @m[1]; */" +
              "  }" +
              "  @ss;" +
              "}"))
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = this.match(node);
        if (bindings != null) {
          Expression k;
          Statement ks = (Statement) bindings.get("k");
          if (ks instanceof ExpressionStmt) {
            k = ((ExpressionStmt) ks).getExpression();
          } else {
            Declaration d = (Declaration) ks;
            if (d.getInitializer() != null
                || d.getIdentifierName().endsWith("__")) {
              return NONE;
            }
            k = new Reference(d.getIdentifier());
            setTaint(k);
            // TODO(mikesamuel): once decls consolidated, no need to add to
            // start of scope.
            scope.addStartOfScopeStatement((Statement) expand(d, scope));
          }
          Reference m = scope.declareStartOfScopeTemp();
          Reference kt = scope.declareStartOfScopeTemp();
          Reference ot = scope.declareStartOfScopeTemp();

          FilePosition unk = FilePosition.UNKNOWN;
          Expression assign1 = Operation.create(unk, Operator.ASSIGN, k, kt);
          setTaint(assign1);
          Expression assign2 = Operation.create(unk, Operator.ASSIGN, k,
              Operation.create(unk, Operator.SQUARE_BRACKET, m,
              new IntegerLiteral(unk, 1)));
          setTaint(assign2);

          ParseTreeNode result = substV(
              "m", m,
              "kt", kt,
              "kts", newExprStmt(kt),
              "ot", ot,
              "o", expand(bindings.get("o"), scope),
              "assign1", newExprStmt((Expression) expand(assign1, scope)),
              "assign2", newExprStmt((Expression) expand(assign2, scope)),
              "ss", expand(bindings.get("ss"), scope));
          return result;
        } else {
          return NONE;
        }
      }
    },

    ////////////////////////////////////////////////////////////////////////
    // try - try/catch/finally constructs
    ////////////////////////////////////////////////////////////////////////

    // TODO: (metaweta) Implement immutability auditor and then only allow
    // throwing immutable objects.
    new Rule() {
      @Override
      @RuleDescription(
          name="tryCatch",
          synopsis="Expand the innards of a try/catch.",
          reason="",
          matches="try { @s0*; } catch (@x) { @s1*; }",
          substitutes="try { @s0*; } catch (@x) { @s1*; }")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          TryStmt t = (TryStmt) node;
          Identifier exceptionName = t.getCatchClause().getException()
              .getIdentifier();
          if (exceptionName.getName().endsWith("__")) {
            mq.addMessage(
                RewriterMessageType.VARIABLES_CANNOT_END_IN_DOUBLE_UNDERSCORE,
                node.getFilePosition(), this, node);
            return node;
          }
          return substV(
            "s0",  withoutNoops(expandAll(bindings.get("s0"), scope)),
            "x",   noexpand((Identifier) bindings.get("x")),
            "s1",  withoutNoops(
                        expandAll(bindings.get("s1"),
                        Scope.fromCatchStmt(scope, t.getCatchClause()))));
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="tryCatchFinally",
          synopsis="Expand the innards of a try/catch/finally.",
          reason="",
          matches="try { @s0*; } catch (@x) { @s1*; } finally { @s2*; }",
          substitutes="try { @s0*; } catch (@x) { @s1*; } finally { @s2*; }")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          TryStmt t = (TryStmt) node;
          Identifier exceptionName = t.getCatchClause().getException()
              .getIdentifier();
          if (exceptionName.getName().endsWith("__")) {
            mq.addMessage(
                RewriterMessageType.VARIABLES_CANNOT_END_IN_DOUBLE_UNDERSCORE,
                node.getFilePosition(), this, node);
            return node;
          }
          return substV(
            "s0",  withoutNoops(expandAll(bindings.get("s0"), scope)),
            "x",   noexpand((Identifier) bindings.get("x")),
            "s1",  withoutNoops(expandAll(
                       bindings.get("s1"),
                       Scope.fromCatchStmt(scope, t.getCatchClause()))),
            "s2",  withoutNoops(expandAll(bindings.get("s2"), scope)));
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="tryFinally",
          synopsis="Expand the innards of a try/finally.",
          reason="",
          matches="try { @s0*; } finally { @s1*; }",
          substitutes="try { @s0*; } finally { @s1*; }")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        return transform(node, scope);
      }
    },

    ////////////////////////////////////////////////////////////////////////
    // variable - variable name handling
    ////////////////////////////////////////////////////////////////////////

    new Rule() {
      @Override
      @RuleDescription(
          name="varArgs",
          synopsis="Make all references to the magic \"arguments\" variable "
              + "into references to a fake arguments object",
          reason="ES3 specifies that the magic \"arguments\" variable is a "
              + "dynamic (\"joined\") mutable array-like reflection of the "
              + "values of the parameter variables. However, the typical usage "
              + "is to pass it to provide access to one's original arguments, "
              + "without the intention of providing the ability to mutate the "
              + "caller's parameter variables. By making a fake arguments "
              + "object with no \"callee\" property, we provide the least "
              + "authority assumed by this typical use.\n"
              + "The fake is made with a \"var a___ = "
              + "___.args(arguments);\" generated at the beginning of the "
              + "function body.",
          matches="arguments",
          substitutes="a___")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        return transform(node, scope);
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="varThis",
          synopsis="Replace \"this\" with \"dis___\".",
          reason="The rules for binding of \"this\" in "
              + "JavaScript are dangerous.",
          matches="this",
          substitutes="dis___")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        return transform(node, scope);
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="varBadSuffix",
          synopsis="Statically reject if a variable with `__` suffix is found.",
          reason="Caja reserves the `__` suffix for internal use.",
          matches="@v__",
          substitutes="<reject>")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          mq.addMessage(
              RewriterMessageType.VARIABLES_CANNOT_END_IN_DOUBLE_UNDERSCORE,
              node.getFilePosition(), this, node);
          return node;
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="varBadSuffixDeclaration",
          synopsis="Statically reject if a variable with `__` suffix is found.",
          reason="Caja reserves the `__` suffix for internal use.",
          matches="<approx> var @v__ ...",
          matchNode=Declaration.class,
          substitutes="<reject>")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        if (node instanceof Declaration) {
          Identifier name = ((Declaration) node).getIdentifier();
          if (name.getValue().endsWith("__")) {
            mq.addMessage(
                RewriterMessageType.VARIABLES_CANNOT_END_IN_DOUBLE_UNDERSCORE,
                node.getFilePosition(), this, node);
            return node;
          }
        }
        return NONE;
      }
    },


    // This rule is separate from varBadSuffixDeclaration, because
    // although FunctionDeclaration is a subclass of Declaration,
    // it fuzzes as a FunctionConstructor.
    new Rule() {
      @Override
      @RuleDescription(
          name="functionBadSuffixDeclaration",
          synopsis="Statically reject if a variable with `__` suffix is found.",
          reason="Caja reserves the `__` suffix for internal use.",
          matches="<approx> function @v__ ...",
          matchNode=FunctionDeclaration.class,
          substitutes="<reject>")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        if (node instanceof FunctionDeclaration) {
          Identifier name = ((FunctionDeclaration) node).getIdentifier();
          String strName = name.getValue();
          if (strName != null && strName.endsWith("__")) {
            mq.addMessage(
                RewriterMessageType.VARIABLES_CANNOT_END_IN_DOUBLE_UNDERSCORE,
                node.getFilePosition(), this, node);
            return node;
          }
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="functionBadSuffixConstructor",
          synopsis="Statically reject if a variable with `__` suffix is found.",
          reason="Caja reserves the `__` suffix for internal use.",
          matches="<approx> function @v__ ...",
          matchNode=FunctionConstructor.class,
          substitutes="<reject>")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        if (node instanceof FunctionConstructor) {
          Identifier name = ((FunctionConstructor) node).getIdentifier();
          String strName = name.getValue();
          if (strName != null && strName.endsWith("__")) {
            mq.addMessage(
                RewriterMessageType.VARIABLES_CANNOT_END_IN_DOUBLE_UNDERSCORE,
                node.getFilePosition(), this, node);
            return node;
          }
        }
        return NONE;
      }
    },

    // TODO(metaweta): Use fastpath
    new Rule() {
      @Override
      @RuleDescription(
          name="varGlobal",
          synopsis="Global vars are rewritten to be properties of IMPORTS___.",
          reason="",
          matches="@v",
          matchNode=Reference.class,
          substitutes="IMPORTS___.@fp ?" +
              "IMPORTS___.@v :" +
              "___.ri(IMPORTS___, @vname)")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          ParseTreeNode v = bindings.get("v");
          if (v instanceof Reference) {
            Reference vRef = (Reference) v;
            if (scope.isOuter(vRef.getIdentifierName())) {
              return substV(
                  "fp", newReference(
                      vRef.getFilePosition(),
                      vRef.getIdentifierName() + "_v___"),
                  "v", noexpand(vRef),
                  "vname", toStringLiteral(v));
            }
          }
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="varDefault",
          synopsis="Any remaining uses of a variable name are preserved.",
          reason="",
          matches="@v",
          matchNode=Reference.class,
          substitutes="@v")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          ParseTreeNode v = bindings.get("v");
          if (v instanceof Reference) {
            return noexpand((Reference) v);
          }
        }
        return NONE;
      }
    },

    ////////////////////////////////////////////////////////////////////////
    // read - reading properties
    ////////////////////////////////////////////////////////////////////////

    new Rule() {
      @Override
      @RuleDescription(
          name="readBadSuffix",
          synopsis="Statically reject if a property has `__` suffix is found.",
          reason="Caja reserves the `__` suffix for internal use.",
          matches="@x.@p__",
          substitutes="<reject>")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          mq.addMessage(
              RewriterMessageType.PROPERTIES_CANNOT_END_IN_DOUBLE_UNDERSCORE,
              node.getFilePosition(), this, node);
          return node;
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="getLength",
          synopsis="",
          reason="Length is whitelisted on Object.prototype",
          matches="@o.length",
          substitutes="@o.length")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        return transform(node, scope);
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="get",
          synopsis="",
          reason="",
          matches="@o.@p",
          substitutes="@oRef.@fp ? @oRef.@p : @oRef.v___('@p')")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          Reference p = (Reference) bindings.get("p");
          String propertyName = p.getIdentifierName();
          Reusable ru = new Reusable(scope, bindings.get("o"));
          ru.generate();
          return commas(ru.init(), (Expression) substV(
              "oRef", ru.ref(0),
              "p",    noexpand(p),
              "fp",   newReference(p.getFilePosition(),
                                   propertyName + "_v___"),
              "rp",   toStringLiteral(p)));
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="readNum",
          synopsis="Recognize that array indexing is inherently safe.",
          reason="When the developer knows that their index expression is" +
              " an array index, they can indicate this with the" +
              " 'known-numeric operator'. Since these" +
              " properties are necessarily readable, we can pass them " +
              " through directly to JavaScript. We don't support Firefox 2," +
              " which exposes authority on negative indices of some objects.",
          matches="@o[+@s]",
          substitutes="@o[+@s]")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        return transform(node, scope);
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="readNumWithConstantIndex",
          synopsis="Recognize that array indexing is inherently safe.",
          reason="Numeric properties are always readable;" +
              " we can pass these through directly to JavaScript." +
              " We don't support Firefox 2 or 3," +
              " which expose authority on negative indices of some objects.",
          matches="@o[@numLiteral]",
          substitutes="@o[@numLiteral]")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          ParseTreeNode index = bindings.get("numLiteral");
          if (index instanceof NumberLiteral) {
            return substV(
                "o", expand(bindings.get("o"), scope),
                "numLiteral", expand(index, scope));
          }
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="readIndex",
          synopsis="",
          reason="",
          matches="@o[@s]",
          substitutes="@o.v___(@s)")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        return transform(node, scope);
      }
    },

    ////////////////////////////////////////////////////////////////////////
    // set - assignments
    ////////////////////////////////////////////////////////////////////////

    new Rule() {
      @Override
      @RuleDescription(
          name="setBadVariable",
          synopsis="Statically reject if an expression assigns to an "
              + "unmaskable variable.",
          reason="arguments and eval are not allowed to be written to.",
          matches="@import = @y",
          substitutes="<reject>")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null && bindings.get("import") instanceof Reference) {
          String name =
              ((Reference) bindings.get("import")).getIdentifierName();
          if (Scope.UNMASKABLE_IDENTIFIERS.contains(name)) {
            mq.addMessage(
                RewriterMessageType.CANNOT_ASSIGN_TO_IDENTIFIER,
                node.getFilePosition(), MessagePart.Factory.valueOf(name));
            return node;
          }
        }
        return NONE;
      }
    },

    // TODO (metaweta): Use fastpath.
    new Rule() {
      @Override
      @RuleDescription(
          name="initGlobalVar",
          synopsis="",
          reason="",
          matches="/* in outer scope */ var @v = @r",
          substitutes="___.di(IMPORTS___, '@v'), IMPORTS___.w___('@v', @r)")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = this.match(node);
        if (bindings != null) {
          Identifier v = (Identifier) bindings.get("v");
          String vname = v.getName();
          if (scope.isOuter(vname)) {
            ParseTreeNode r = bindings.get("r");
            mq.addMessage(
                RewriterMessageType.TOP_LEVEL_VAR_INCOMPATIBLE_WITH_CAJA,
                node.getFilePosition(),
                MessagePart.Factory.valueOf(
                    render(QuasiBuilder.substV("window['@v']", "v", v))
                    + " = ..."));
            return newExprStmt((Expression) substV(
                "v", v,
                "r", expand(nymize(r, vname, "var"), scope)));
          }
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="setBadGlobalVar",
          synopsis="Statically reject if a global with `__` suffix is found.",
          reason="Caja reserves the `__` suffix for internal use.",
          matches="/* declared in outer scope */ @v__ = @r",
          substitutes="<reject>")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          mq.addMessage(
              RewriterMessageType.VARIABLES_CANNOT_END_IN_DOUBLE_UNDERSCORE,
              node.getFilePosition(), this, node);
          return node;
        }
        return NONE;
      }
    },

    // TODO (metaweta): Use fastpath.
    new Rule() {
      @Override
      @RuleDescription(
          name="setGlobalVar",
          synopsis="",
          reason="",
          matches="/* declared in outer scope */ @v = @r",
          substitutes="IMPORTS___.@fp ?" +
              "IMPORTS___.@v = @r :" +
              "___.wi(IMPORTS___, '@v', @r)")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = this.match(node);
        if (bindings != null) {
          ParseTreeNode v = bindings.get("v");
          if (v instanceof Reference) {
            Reference vRef = (Reference) v;
            String vname = vRef.getIdentifierName();
            if (scope.isOuter(vname)) {
              ParseTreeNode r = bindings.get("r");
              return substV(
                  "v", noexpand(vRef),
                  "fp", newReference(
                      vRef.getFilePosition(),
                      vRef.getIdentifierName() + "_w___"),
                  "r", expand(nymize(r, vname, "var"), scope));
            }
          }
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="declGlobalVar",
          synopsis="",
          reason="",
          matches="/* in outer scope */ var @v",
          substitutes="___.di(IMPORTS___, '@v')")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = this.match(node);
        if (bindings != null &&
            bindings.get("v") instanceof Identifier &&
            scope.isOuter(((Identifier) bindings.get("v")).getName())) {
          mq.addMessage(
              RewriterMessageType.TOP_LEVEL_VAR_INCOMPATIBLE_WITH_CAJA,
              node.getFilePosition(),
              MessagePart.Factory.valueOf(
                  render(QuasiBuilder.substV("window['@v'] = undefined",
                      "v", bindings.get("v")))));
          ExpressionStmt es = newExprStmt(
              (Expression) substV("v", bindings.get("v")));
          markTreeForSideEffect(es);
          return es;
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="setBadSuffix",
          synopsis="Statically reject if a property with `__` suffix is found.",
          reason="Caja reserves the `__` suffix for internal use.",
          matches="@x.@p__ = @z",
          substitutes="<reject>")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          mq.addMessage(
              RewriterMessageType.PROPERTIES_CANNOT_END_IN_DOUBLE_UNDERSCORE,
              node.getFilePosition(), this, node);
          return node;
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="set",
          synopsis="Set a property.",
          reason="",
          matches="@o.@p = @r",
          substitutes="<approx> @o.w___(@'p', @r);")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          Reference p = (Reference) bindings.get("p");
          String propertyName = p.getIdentifierName();
          ParseTreeNode r = nymize(bindings.get("r"), propertyName, "meth");
          Reusable ru = new Reusable(scope, bindings.get("o"), r);
          ru.generate();
          return commas(ru.init(), (Expression) QuasiBuilder.substV(
              "@oRef.@pWritable === @oRef ? (@oRef.@p = @rRef) : " +
              "                           @oRef.w___(@pName, @rRef);",
              "oRef", ru.ref(0),
              "rRef", ru.ref(1),
              "pWritable", newReference(UNK, propertyName + "_w___"),
              "p", noexpand(p),
              "pName", toStringLiteral(p)));
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="setNumericIndex",
          synopsis="Set a property marked as numeric.",
          reason="",
          matches="@o[+@p] = @r",
          substitutes="<approx> @o.w___(+@p, @r);")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          Expression p = (Expression) bindings.get("p");
          ParseTreeNode r = nymize(bindings.get("r"), "", "meth");
          Reusable ru = new Reusable(scope, bindings.get("o"), r);
          ru.generate();
          return commas(ru.init(), (Expression) QuasiBuilder.substV(
              "@oRef.NUM____w___ === @oRef ? (@oRef[+@p] = @rRef) : " +
              "                           @oRef.w___(+@p, @rRef);",
              "oRef", ru.ref(0),
              "rRef", ru.ref(1),
              "p", expand(p, scope)));
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="setNumericLiteralIndex",
          synopsis="Set a numeric literal property.",
          reason="",
          matches="@o[@numLiteral] = @r",
          substitutes="<approx> @o.w___(@numLiteral, @r);")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          ParseTreeNode index = bindings.get("numLiteral");
          if (index instanceof NumberLiteral) {
            ParseTreeNode r = nymize(
                bindings.get("r"), index.toString(), "meth");
            Reusable ru = new Reusable(scope, bindings.get("o"), r);
            ru.generate();
            return commas(ru.init(), (Expression) QuasiBuilder.substV(
                "(@oRef.NUM____w___ === @oRef) ? " +
                "    (@oRef[@numLiteral] = @rRef) : " +
                "    @oRef.w___(@numLiteral, @rRef);",
                "oRef", ru.ref(0),
                "rRef", ru.ref(1),
                "numLiteral", noexpand((NumberLiteral)index)));
          }
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="setIndex",
          synopsis="",
          reason="",
          matches="@o[@s] = @r",
          substitutes="@o.w___(@s, @r)")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          return substV(
              "o", expand(bindings.get("o"), scope),
              "s", expand(bindings.get("s"), scope),
              "r", expand(bindings.get("r"), scope));
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="setBadInitialize",
          synopsis="Statically reject if a variable with `__` suffix is found.",
          reason="Caja reserves the `__` suffix for internal use.",
          matches="var @v__ = @r",
          substitutes="<reject>")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          mq.addMessage(
              RewriterMessageType.VARIABLES_CANNOT_END_IN_DOUBLE_UNDERSCORE,
              node.getFilePosition(), this, node);
          return node;
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="setInitialize",
          synopsis="Ensure v is not a function name. Expand the right side.",
          reason="vars and functions have different scoping.",
          matches="var @v = @r",
          substitutes="@v = @r")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          Identifier v = (Identifier) bindings.get("v");
          if (!scope.isFunction(v.getName())) {
            ParseTreeNode r = bindings.get("r");
            scope.addStartOfScopeStatement(
                new Declaration(v.getFilePosition(), v, null));
            ExpressionStmt init = new ExpressionStmt(
                node.getFilePosition(), (Expression) substV(
                    "v", new Reference(noexpand(v)),
                    "r", expand(nymize(r, v.getName(), "var"), scope)));
            // The result of the initializer of a declaration is not relevant to
            // the module result.
            markTreeForSideEffect(init);
            return init;
          }
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="setBadDeclare",
          synopsis="Statically reject if a variable with `__` suffix is found.",
          reason="Caja reserves the `__` suffix for internal use.",
          matches="var @v__",
          substitutes="<reject>")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          mq.addMessage(
              RewriterMessageType.VARIABLES_CANNOT_END_IN_DOUBLE_UNDERSCORE,
              node.getFilePosition(), this, node);
          return node;
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="setDeclare",
          synopsis="Ensure that v isn't a function name.",
          reason="vars and functions have different scoping.",
          matches="var @v",
          substitutes="var @v")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          Identifier id = (Identifier) bindings.get("v");
          if (!scope.isFunction(id.getName())) {
            scope.addStartOfScopeStatement((Declaration) substV(
                "v", noexpand((Identifier) bindings.get("v"))));
            return new Noop(node.getFilePosition());
          }
        }
        return NONE;
      }
    },

    // TODO(erights): Need a general way to expand lValues
    new Rule() {
      @Override
      @RuleDescription(
          name="setBadVar",
          synopsis="Statically reject if a variable with `__` suffix is found.",
          reason="Caja reserves the `__` suffix for internal use.",
          matches="@v__ = @r",
          substitutes="<reject>")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          mq.addMessage(
              RewriterMessageType.VARIABLES_CANNOT_END_IN_DOUBLE_UNDERSCORE,
              node.getFilePosition(), this, node);
          return node;
        }
        return NONE;
      }
    },

    // TODO(erights): Need a general way to expand lValues
    new Rule() {
      @Override
      @RuleDescription(
          name="setVar",
          synopsis="Plain old assignment.",
          reason="",
          matches="@v = @r",
          substitutes="@v = @r")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          ParseTreeNode v = bindings.get("v");
          if (v instanceof Reference) {
            String vname = getReferenceName(v);
            ParseTreeNode r = bindings.get("r");
            return substV(
                "v", noexpand((Reference) v),
                "r", expand(nymize(r, vname, "var"), scope));
          }
        }
        return NONE;
      }
    },

    // TODO(erights): Need a general way to expand readModifyWrite lValues.
    // For now, we're just picking off a few common special cases as they
    // come up.

    new Rule() {
      @Override
      @RuleDescription(
          name="setReadModifyWriteLocalVar",
          synopsis="",
          reason="",
          matches="<approx> @x @op= @y",
          matchNode=AssignOperation.class,
          substitutes="<approx> @x = @x @op @y")
      // Handle x += 3 and similar ops by rewriting them using the assignment
      // delegate, "x += y" => "x = x + y", with deconstructReadAssignOperand
      // assuring that x is evaluated at most once where that matters.
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        if (node instanceof AssignOperation) {
          AssignOperation aNode = (AssignOperation) node;
          Operator op = aNode.getOperator();
          if (op.getAssignmentDelegate() == null) { return NONE; }

          ReadAssignOperands ops = deconstructReadAssignOperand(
              aNode.children().get(0), scope);
          if (ops == null) { return node; // Error deconstructing

          // For x += 3, rhs is (x + 3)
          Operation rhs = Operation.create(
              aNode.children().get(0).getFilePosition(),
              op.getAssignmentDelegate(),
              ops.getUncajoledLValue(), aNode.children().get(1));
          Operation assignment = ops.makeAssignment(rhs);
          return commas(newCommaOperation(ops.getTemporaries()),
                        (Expression) expand(assignment, scope));
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="setIncrDecr",
          synopsis="Handle pre and post ++ and --.",
          matches="<approx> ++@x but any {pre,post}{in,de}crement will do",
          matchNode=AssignOperation.class,
          reason="")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        if (!(node instanceof AssignOperation)) { return NONE; }
        AssignOperation op = (AssignOperation) node;
        Expression v = op.children().get(0);
        ReadAssignOperands ops = deconstructReadAssignOperand(v, scope);
        if (ops == null) { return node; // Error deconstructing

        // TODO(mikesamuel): Figure out when post increments are being
        // used without use of the resulting value and switch them to
        // pre-increments.
        switch (op.getOperator()) {
          case POST_INCREMENT:
            if (ops.isSimpleLValue()) {
              return QuasiBuilder.substV("@v ++", "v", ops.getCajoledLValue());
            } else {
              Reference tmpVal = scope.declareStartOfScopeTemp();
              Expression assign = (Expression) expand(
                  ops.makeAssignment((Expression) QuasiBuilder.substV(
                      "@tmpVal + 1", "tmpVal", tmpVal)),
                  scope);
              return QuasiBuilder.substV(
                  "  @tmps,"
                  + "@tmpVal = +@rvalue,"  // Coerce to a number.
                  + "@assign,"  // Assign value.
                  + "@tmpVal",
                  "tmps", newCommaOperation(ops.getTemporaries()),
                  "tmpVal", tmpVal,
                  "rvalue", ops.getCajoledLValue(),
                  "assign", assign);
            }
          case PRE_INCREMENT:
            // We subtract -1 instead of adding 1 since the - operator coerces
            // to a number in the same way the ++ operator does.
            if (ops.isSimpleLValue()) {
              return QuasiBuilder.substV("++@v", "v", ops.getCajoledLValue());
            } else if (ops.getTemporaries().isEmpty()) {
              return expand(
                  ops.makeAssignment((Expression) QuasiBuilder.substV(
                      "@rvalue - -1",
                      "rvalue", ops.getUncajoledLValue())),
                  scope);
            } else {
              return QuasiBuilder.substV(
                  "  @tmps,"
                  + "@assign",
                  "tmps", newCommaOperation(ops.getTemporaries()),
                  "assign", expand(
                      ops.makeAssignment((Expression) QuasiBuilder.substV(
                          "@rvalue - -1", "rvalue", ops.getUncajoledLValue())),
                      scope));
            }
          case POST_DECREMENT:
            if (ops.isSimpleLValue()) {
              return QuasiBuilder.substV("@v--", "v", ops.getCajoledLValue());
            } else {
              Reference tmpVal = new Reference(
                  scope.declareStartOfScopeTempVariable());
              Expression assign = (Expression) expand(
                  ops.makeAssignment((Expression) QuasiBuilder.substV(
                      "@tmpVal - 1", "tmpVal", tmpVal)),
                  scope);
              return QuasiBuilder.substV(
                  "  @tmps,"
                  + "@tmpVal = +@rvalue,"  // Coerce to a number.
                  + "@assign,"  // Assign value.
                  + "@tmpVal;",
                  "tmps", newCommaOperation(ops.getTemporaries()),
                  "tmpVal", tmpVal,
                  "rvalue", ops.getCajoledLValue(),
                  "assign", assign);
            }
          case PRE_DECREMENT:
            if (ops.isSimpleLValue()) {
              return QuasiBuilder.substV("--@v", "v", ops.getCajoledLValue());
            } else if (ops.getTemporaries().isEmpty()) {
              return expand(
                  ops.makeAssignment(
                      (Expression) QuasiBuilder.substV(
                          "@rvalue - 1",
                          "rvalue", ops.getUncajoledLValue())),
                  scope);
            } else {
              return QuasiBuilder.substV(
                  "  @tmps,"
                  + "@assign",
                  "tmps", newCommaOperation(ops.getTemporaries()),
                  "assign", expand(
                      ops.makeAssignment((Expression) QuasiBuilder.substV(
                          "@rvalue - 1",
                          "rvalue", ops.getUncajoledLValue())),
                      scope));
            }
          default:
            return NONE;
        }
      }
    },

    ////////////////////////////////////////////////////////////////////////
    // new - new object creation
    ////////////////////////////////////////////////////////////////////////

    new Rule() {
      @Override
      @RuleDescription(
          name="newCalllessCtor",
          synopsis="Add missing empty argument list.",
          reason="JavaScript syntax allows constructor calls without \"()\".",
          matches="new @ctor",
          substitutes="new @ctor.new___()")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        return transform(node, scope);
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="newCtor",
          synopsis="",
          reason="",
          matches="new @ctor(@as*)",
          substitutes="new @ctor.new___(@as*)")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        return transform(node, scope);
      }
    },

    ////////////////////////////////////////////////////////////////////////
    // delete - property deletion
    ////////////////////////////////////////////////////////////////////////

    new Rule() {
      @Override
      @RuleDescription(
          name="deleteBadSuffix",
          synopsis="",
          reason="",
          matches="delete @o.@p__",
          substitutes="<reject>")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          mq.addMessage(
              RewriterMessageType.PROPERTIES_CANNOT_END_IN_DOUBLE_UNDERSCORE,
              node.getFilePosition(), this, node);
          return node;
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="delete",
          synopsis="",
          reason="",
          matches="delete @o.@p",
          substitutes="@o.c___(@'p')")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          Reference p = (Reference) bindings.get("p");
          return QuasiBuilder.substV(
              "@o.c___(@pname)",
              "o", expand(bindings.get("o"), scope),
              "pname", toStringLiteral(p));
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="deleteIndex",
          synopsis="",
          reason="",
          matches="delete @o[@s]",
          substitutes="@o.c___(@s)")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          return substV(
              "o", expand(bindings.get("o"), scope),
              "s", expand(bindings.get("s"), scope));
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="deleteNonProperty",
          synopsis="",
          reason="",
          matches="delete @v",
          substitutes="<reject>")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          mq.addMessage(
              RewriterMessageType.NOT_DELETABLE, node.getFilePosition());
          return node;
        }
        return NONE;
      }
    },

    ////////////////////////////////////////////////////////////////////////
    // call - function calls
    ////////////////////////////////////////////////////////////////////////

    new Rule() {
      @Override
      @RuleDescription(
          name="callBadSuffix",
          synopsis="Statically reject if a selector with `__` suffix is found.",
          reason="Caja reserves the `__` suffix for internal use.",
          matches="@o.@p__(@as*)",
          substitutes="<reject>")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          mq.addMessage(
              RewriterMessageType.SELECTORS_CANNOT_END_IN_DOUBLE_UNDERSCORE,
              node.getFilePosition(), this, node);
          return node;
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="callMethod",
          synopsis="",
          reason="",
          matches="@o.@m(@as*)",
          substitutes="<approx> @o.m___(@'m', [@as*])")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          Reference m = (Reference) bindings.get("m");
          String methodName = m.getIdentifierName();
          Reusable ru = new Reusable(scope, bindings.get("o"));
          ru.addChildren(bindings.get("as"));
          ru.generate();
          return commas(ru.init(), (Expression) QuasiBuilder.substV(
              "@oRef.@fm ? @oRef.@m(@argRefs*) : @oRef.m___(@rm, [@argRefs*]);",
              "oRef",    ru.ref(0),
              "argRefs", ru.refListFrom(1),
              "m",       noexpand(m),
              "fm",      newReference(UNK, methodName + "_m___"),
              "rm",      toStringLiteral(m)));
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="callIndexedMethod",
          synopsis="",
          reason="",
          matches="@o[@s](@as*)",
          substitutes="@o.m___(@s, [@as*])")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        return transform(node, scope);
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="callFunc",
          synopsis="",
          reason="",
          matches="@f(@as*)",
          substitutes="@f.i___(@as*)")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        return transform(node, scope);
      }
    },

    ////////////////////////////////////////////////////////////////////////
    // function - function definitions
    ////////////////////////////////////////////////////////////////////////

    // TODO(metaweta): Do a lighter-weight wrapping when the function
    // does not use {@code this}.

    new Rule() {
      @Override
      @RuleDescription(
          name="funcAnonSimple",
          synopsis="",
          reason="",
          matches="function (@ps*) { @bs*; }",
          substitutes=""
              + "___.f(function (@ps*) {\n"
              + "  @fh*;\n"
              + "  @stmts*;\n"
              + "  @bs*;\n"
              + "})")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        // Anonymous simple function constructor
        if (bindings != null) {
          Scope s2 = Scope.fromFunctionConstructor(
              scope,
              (FunctionConstructor) node);
          ParseTreeNodeContainer ps =
              (ParseTreeNodeContainer) bindings.get("ps");
          checkFormals(ps);
          return substV(
              "ps", noexpandParams(ps),
              // It's important to expand bs before computing fh and stmts.
              "bs", withoutNoops(expand(bindings.get("bs"), s2)),
              "fh", getFunctionHeadDeclarations(s2),
              "stmts", new ParseTreeNodeContainer(s2.getStartStatements()));
        }
        return NONE;
      }
    },

    // References to {@code fname} in the body should refer to the result of
    // {@code wrap}.
    //
    // While under FF3.6/Firebug 1.5 there don't seem to be any issues,
    // this translation has triggered the "cannot access optimized closure"
    // bug on other version combinations.
    // https://bugzilla.mozilla.org/show_bug.cgi?id=505001
    // Using a Y combinator can fix that, but is harder to understand.
    // TODO(metaweta): Only use closure if it's really recursive.
    new Rule() {
      @Override
      @RuleDescription(
          name="funcNamedDecl",
          synopsis="",
          reason="",
          matches="function @fname(@ps*) { @bs*; }",
          substitutes=""
            + "function @fname(@ps*) {\n"
            + "  @fh*;\n"
            + "  @stmts*;\n"
            + "  @bs*;\n"
            + "}\n"
            + "___.f(@fRef, '@fname');")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        if (node instanceof FunctionDeclaration && !scope.isOuter()) {
          Map<String, ParseTreeNode> bindings = match(
              ((FunctionDeclaration) node).getInitializer());
          // Named simple function declaration
          if (bindings != null) {
            Scope s2 = Scope.fromFunctionConstructor(
                scope, ((FunctionDeclaration) node).getInitializer());
            ParseTreeNodeContainer ps =
                (ParseTreeNodeContainer) bindings.get("ps");
            checkFormals(ps);
            Identifier fname = noexpand((Identifier) bindings.get("fname"));
            scope.declareStartOfScopeVariable(fname);
            Statement stmt = (Statement) substV(
                "fname", fname,
                "fRef", new Reference(fname),
                "ps", noexpandParams(ps),
                // It's important to expand bs before computing fh and stmts.
                "bs", withoutNoops(expand(bindings.get("bs"), s2)),
                "fh", getFunctionHeadDeclarations(s2),
                "stmts", new ParseTreeNodeContainer(s2.getStartStatements()));
            scope.addStartStatement(stmt);
            return new Noop(FilePosition.UNKNOWN);
          }
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="funcNamedTopDecl",
          synopsis="",
          reason="",
          matches="function @fname(@ps*) { @bs*; }",
          substitutes=""
            + "function @fname(@ps*) {\n"
            + "  @fh*;\n"
            + "  @stmts*;\n"
            + "  @bs*;\n"
            + "}\n"
            + "IMPORTS___.w___('@fname', ___.f(@fRef, '@fname'));")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        if (node instanceof FunctionDeclaration && scope.isOuter()) {
          Map<String, ParseTreeNode> bindings = match(
              ((FunctionDeclaration) node).getInitializer());
          // Named simple function declaration
          if (bindings != null) {
            Scope s2 = Scope.fromFunctionConstructor(
                scope, ((FunctionDeclaration) node).getInitializer());
            ParseTreeNodeContainer ps =
                (ParseTreeNodeContainer) bindings.get("ps");
            checkFormals(ps);
            Identifier fname = noexpand((Identifier) bindings.get("fname"));
            mq.addMessage(
                RewriterMessageType.TOP_LEVEL_FUNC_INCOMPATIBLE_WITH_CAJA,
                node.getFilePosition(),
                MessagePart.Factory.valueOf(
                    render(QuasiBuilder.substV(
                        "window['@fname'] = function (@ps*) { /* ... */ }",
                        "fname", fname,
                        "ps", bindings.get("ps")))));
            Statement stmt = (Statement) substV(
                "fname", fname,
                "fRef", new Reference(fname),
                "ps", noexpandParams(ps),
                // It's important to expand bs before computing fh and stmts.
                "bs", withoutNoops(expand(bindings.get("bs"), s2)),
                "fh", getFunctionHeadDeclarations(s2),
                "stmts", new ParseTreeNodeContainer(s2.getStartStatements()));
            scope.addStartStatement(stmt);
            return new Noop(FilePosition.UNKNOWN);
          }
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="funcNamedValue",
          synopsis="",
          reason="",
          matches="function @fname(@ps*) { @bs*; }",
          substitutes=""
              + "(function () {\n"
              + "  function @fname(@ps*) {\n"
              + "    @fh*;\n"
              + "    @stmts*;\n"
              + "    @bs*;\n"
              + "  }\n"
              + "  return ___.f(@fRef, '@fname');"
              + "})()")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        // Named simple function expression
        if (bindings != null) {
          Scope s2 = Scope.fromFunctionConstructor(
              scope, (FunctionConstructor) node);
          ParseTreeNodeContainer ps =
              (ParseTreeNodeContainer) bindings.get("ps");
          checkFormals(ps);
          Identifier fname = noexpand((Identifier) bindings.get("fname"));
          return substV(
              "fname", fname,
              "fRef", new Reference(fname),
              "ps", noexpandParams(ps),
              // It's important to expand bs before computing fh and stmts.
              "bs", withoutNoops(expand(bindings.get("bs"), s2)),
              "fh", getFunctionHeadDeclarations(s2),
              "stmts", new ParseTreeNodeContainer(s2.getStartStatements()));
        }
        return NONE;
      }
    },

    ////////////////////////////////////////////////////////////////////////
    // multiDeclaration - multiple declarations
    ////////////////////////////////////////////////////////////////////////

    new Rule() {
      @Override
      @RuleDescription(
          name="multiDeclaration",
          synopsis="Consider declarations separately from initializers",
          reason="",
          matches="var @a=@b?, @c=@d*",
          substitutes="{ @decl; @init; }")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        if (node instanceof MultiDeclaration) {
          Expression initializer = null;
          for (Declaration d : ((MultiDeclaration) node).children()) {
            Statement s = (Statement) expand(d, scope);
            if (s instanceof Noop) { continue; }
            if (s instanceof ExpressionStmt) {
              Expression init = ((ExpressionStmt) s).getExpression();
              initializer = initializer == null
                  ? init
                  : Operation.createInfix(Operator.COMMA, initializer, init);
            } else {
              requireErrors(mq, s);
              return node;
            }
          }
          if (initializer == null) {
            return new Noop(node.getFilePosition());
          } else {
            ExpressionStmt es = new ExpressionStmt(
                node.getFilePosition(), initializer);
            // The value of a declaration is not the value of the initializer.
            markTreeForSideEffect(es);
            return es;
          }
        }
        return NONE;
      }
    },

    ////////////////////////////////////////////////////////////////////////
    // map - object literals
    ////////////////////////////////////////////////////////////////////////

    new Rule() {
      @Override
      @RuleDescription(
          name="mapBadKeySuffix",
          synopsis="Statically reject a property whose name ends with `__`",
          reason="",
          matches="<approx> \"@k__\": @v",
          matchNode=ObjProperty.class,
          substitutes="<reject>")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        if (node instanceof ObjProperty) {
          ObjProperty prop = (ObjProperty) node;
          StringLiteral key = prop.getPropertyNameNode();
          if (key.getUnquotedValue().endsWith("__")) {
            mq.addMessage(
                RewriterMessageType.PROPERTIES_CANNOT_END_IN_DOUBLE_UNDERSCORE,
                key.getFilePosition(), this, key);
            return node;
          }
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="objectProperty",
          synopsis="nymize object properties",
          reason="",
          matches="<approx> \"@k\": @v",
          matchNode=ObjProperty.class,
          substitutes="<nymized>")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        if (node instanceof ObjProperty) {
          if (node instanceof ValueProperty) {
            ValueProperty prop = (ValueProperty) node;
            return new ParseTreeNodeContainer(Arrays.asList(
                 noexpand(prop.getPropertyNameNode()),
                 expand(
                     nymize(prop.getValueExpr(), prop.getPropertyName(), "lit"),
                     scope)));
          } else {
            StringLiteral k = (StringLiteral) node.children().get(0);
            String kType = (node instanceof GetterProperty ? "get" : "set");
            String kName = k.getUnquotedValue() +
                (node instanceof GetterProperty ? "_g___" : "_s___");
            Expression v = (Expression) node.children().get(1);
            return new ParseTreeNodeContainer(Arrays.asList(
                QuasiBuilder.substV("[@k, '" + kType + "']", "k", noexpand(k)),
                expand(nymize(v, kName, "lit"), scope)));
          }
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="map",
          synopsis="Turns an object literal into an explicit initialization.",
          reason="",
          matches="({@key*: @val*})",
          substitutes="___.iM([@parts*])")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        if (node instanceof ObjectConstructor) {
          ObjectConstructor obj = (ObjectConstructor) node;
          List<? extends ObjProperty> props = obj.children();
          List<Expression> expanded = Lists.newArrayList();
          for (ObjProperty prop : props) {
            ParseTreeNode nymized = expand(prop, scope);
            if (nymized instanceof ParseTreeNodeContainer) {
              // Non error property handling case above.
              for (ParseTreeNode child : nymized.children()) {
                expanded.add((Expression) child);
              }
            }
          }
          return substV("parts", new ParseTreeNodeContainer(expanded));
        }
        return NONE;
      }
    },
    ////////////////////////////////////////////////////////////////////////
    // other - things not otherwise covered
    ////////////////////////////////////////////////////////////////////////

    new Rule() {
      @Override
      @RuleDescription(
          name="typeofGlobal",
          synopsis="Don't throw a ReferenceError",
          reason="",
          matches="typeof @v",
          substitutes="typeof @v")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        Map<String, ParseTreeNode> bindings = match(node);
        if (bindings != null) {
          ParseTreeNode v = bindings.get("v");
          if (v instanceof Reference) {
            Reference vRef = (Reference) v;
            if (scope.isOuter(vRef.getIdentifierName())) {
              return QuasiBuilder.substV(
                  "typeof IMPORTS___.v___(@vname)",
                  "vname", toStringLiteral(v));
            }
          }
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="typeof",
          synopsis="Typeof translates simply",
          reason="",
          matches="typeof @v",
          substitutes="typeof @v")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        return transform(node, scope);
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="in",
          synopsis="Is a property present on the object?",
          reason="",
          matches="@i in @o",
          substitutes="___.i('' + @i, @o)")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        return transform(node, scope);
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="voidOp",
          synopsis="",
          reason="",
          matches="void @x",
          substitutes="void @x")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        return transform(node, scope);
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="commaOp",
          synopsis="",
          reason="",
          matches="(@a, @b)",
          substitutes="(@a, @b)")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        return transform(node, scope);
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="breakStmt",
          synopsis="disallow labels that end in __",
          reason="",
          matches="break @a;",
          substitutes="break @a;")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        if (node instanceof BreakStmt) {
          String label = ((BreakStmt) node).getLabel();
          if (label.endsWith("__")) {
            mq.addMessage(
                RewriterMessageType.LABELS_CANNOT_END_IN_DOUBLE_UNDERSCORE,
                node.getFilePosition(),
                MessagePart.Factory.valueOf(label));
          }
          return noexpand((BreakStmt) node);
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="continueStmt",
          synopsis="disallow labels that end in __",
          reason="",
          matches="continue @a;",
          substitutes="continue @a;")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        if (node instanceof ContinueStmt) {
          String label = ((ContinueStmt) node).getLabel();
          if (label.endsWith("__")) {
            mq.addMessage(
                RewriterMessageType.LABELS_CANNOT_END_IN_DOUBLE_UNDERSCORE,
                node.getFilePosition(),
                MessagePart.Factory.valueOf(label));
          }
          return noexpand((ContinueStmt) node);
        }
        return NONE;
      }
    },

    new Rule() {
      @Override
      @RuleDescription(
          name="regexLiteral",
          synopsis="Use the regular expression constructor",
          reason="So that every use of a regex literal creates a new instance"
              + " to prevent state from leaking via interned literals. This"
              + " is consistent with the way ES4 treates regex literals.",
          matchNode=RegexpLiteral.class,
          substitutes="new RegExp.new___(@pattern, @modifiers?)")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        if (node instanceof RegexpLiteral) {
          RegexpLiteral.RegexpWrapper re = ((RegexpLiteral) node).getValue();
          FilePosition pos = node.getFilePosition();
          StringLiteral pattern = StringLiteral.valueOf(pos, re.getMatchText());
          StringLiteral modifiers = !"".equals(re.getModifiers())
              ? StringLiteral.valueOf(pos, re.getModifiers()) : null;
          return substV(
              "pattern", pattern,
              "modifiers", modifiers);
        }
        return NONE;
      }
    },

    // TODO: use a whitelist, deactivate the rest by replacing with
    // {@code '' + @directive}
    new Rule() {
      @Override
      @RuleDescription(
          name="useSubsetDirective",
          synopsis="replace use subset directives with noops",
          reason="rewriting changes the block structure of the input, which"
              + " could lead to a directive appearing in an illegal position"
              + " since directives must appear at the beginning of a program"
              + " or function body, not in an arbitrary block",
          matches="<approx> 'use';",
          matchNode=DirectivePrologue.class,
          substitutes=";")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        if (node instanceof DirectivePrologue) {
          return new Noop(node.getFilePosition());
        }
        return NONE;
      }
    },

    ////////////////////////////////////////////////////////////////////////
    // recurse - automatically recurse into some structures
    ////////////////////////////////////////////////////////////////////////

    new Rule() {
      @Override
      @RuleDescription(
          name="recurse",
          synopsis="Automatically recurse into some structures",
          reason="",
          matches="<many>")
      public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
        if (node instanceof ParseTreeNodeContainer ||
            node instanceof ArrayConstructor ||
            node instanceof Elision ||
            node instanceof CaseStmt ||
            node instanceof Conditional ||
            node instanceof DebuggerStmt ||
            node instanceof DefaultCaseStmt ||
            node instanceof ExpressionStmt ||
            node instanceof FormalParam ||
            node instanceof Identifier ||
            node instanceof LabeledStmtWrapper ||
            node instanceof Literal ||
            node instanceof Loop ||
            node instanceof Noop ||
            node instanceof SimpleOperation ||
            node instanceof ControlOperation ||
            node instanceof ReturnStmt ||
            node instanceof SwitchStmt ||
            node instanceof ThrowStmt) {
          return expandAll(node, scope);
        }
        return NONE;
      }
    }
  };

  public ES53Rewriter(
      URI baseUri, ModuleManager moduleManager, boolean logging) {
    super(null == moduleManager ? null : moduleManager.getMessageQueue(),
        true, logging);
    this.buildInfo =
      null == moduleManager ? null : moduleManager.getBuildInfo();
    initRules();
  }

  public ES53Rewriter(BuildInfo buildInfo, MessageQueue mq, boolean logging) {
    super(mq, true, logging);
    this.buildInfo = buildInfo;
    initRules();
  }

  private void initRules() {
    addRules(SyntheticRuleSet.syntheticRules(this));
    addRules(cajaRules);
  }

  private static void requireErrors(MessageQueue mq, ParseTreeNode n) {
    // Make sure a sub-rule has put an error because the rule got an unexpected
    // result from a recursive call to expand.
    if (!mq.hasMessageAtLevel(MessageLevel.ERROR)) {
      mq.addMessage(
          RewriterMessageType.BAD_RESULT_FROM_RECURSIVE_CALL,
          n.getFilePosition(), n);
    }
  }
}
TOP

Related Classes of com.google.caja.parser.quasiliteral.ES53Rewriter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.