From cc38f71e62ded3dfacd0d1449025fdb68a71aad4 Mon Sep 17 00:00:00 2001 From: mattmm Date: Mon, 2 Jul 2018 15:58:34 -0700 Subject: [PATCH] Provide two modes for Es6RewriteDestructuring * rewrite all * rewrite only patterns containing object rest and a Builder to specify the desired rewrite mode. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=203027589 --- .../jscomp/Es6RewriteDestructuring.java | 280 ++++++++- .../jscomp/EsNextToEs8Converter.java | 461 +------------- .../jscomp/TranspilationPasses.java | 50 +- .../jscomp/Es6RewriteDestructuringTest.java | 567 +++++++++++++++++- .../jscomp/EsNextToEs8ConverterTest.java | 393 ------------ .../javascript/jscomp/IntegrationTest.java | 32 +- .../javascript/jscomp/MultiPassTest.java | 6 +- 7 files changed, 870 insertions(+), 919 deletions(-) diff --git a/src/com/google/javascript/jscomp/Es6RewriteDestructuring.java b/src/com/google/javascript/jscomp/Es6RewriteDestructuring.java index f5c1a96ff17..a983e2040a5 100644 --- a/src/com/google/javascript/jscomp/Es6RewriteDestructuring.java +++ b/src/com/google/javascript/jscomp/Es6RewriteDestructuring.java @@ -17,6 +17,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; +import static com.google.javascript.jscomp.DiagnosticType.error; import static com.google.javascript.jscomp.Es6ToEs3Util.arrayFromIterator; import static com.google.javascript.jscomp.Es6ToEs3Util.makeIterator; @@ -28,46 +29,155 @@ import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.TokenStream; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.Iterator; /** - * Rewrites ES6 destructuring patterns and default parameters to valid ES3 code. + * Rewrites destructuring patterns and default parameters to valid ES3 code or to a different form + * of destructuring. */ +// TODO(mattmm): Rename class to drop Es6 prefix public final class Es6RewriteDestructuring implements NodeTraversal.Callback, HotSwapCompilerPass { + + public static final DiagnosticType UNEXPECTED_DESTRUCTURING_REST_PARAMETER = + error( + "JSC_UNEXPECTED_DESTRUCTURING_REST_PARAMETER", + "Es6RewriteDestructuring not expecting object pattern rest parameter"); + + enum ObjectDestructuringRewriteMode { + /** + * Rewrite all object destructuring patterns. This is the default mode used if no + * ObjectDestructuringRewriteMode is provided to the Builder. + * + *

Used to transpile ES2018 -> ES5 + */ + REWRITE_ALL_OBJECT_PATTERNS, + + /** + * Rewrite only destructuring patterns that contain object pattern rest properties (whether the + * rest is on the top level or nested within a property). + * + *

Used to transpile ES2018 -> ES2017 + */ + REWRITE_OBJECT_REST, + } + private final AbstractCompiler compiler; - private static final FeatureSet transpiledFeatures = - FeatureSet.BARE_MINIMUM.with( - Feature.DEFAULT_PARAMETERS, Feature.DESTRUCTURING, Feature.ARRAY_PATTERN_REST); + private final ObjectDestructuringRewriteMode rewriteMode; + + private final FeatureSet featuresToTriggerRunningPass; + private final FeatureSet featuresToMarkAsRemoved; + + private final Deque patternNestingStack = new ArrayDeque<>(); static final String DESTRUCTURING_TEMP_VAR = "$jscomp$destructuring$var"; private int destructuringVarCounter = 0; - public Es6RewriteDestructuring(AbstractCompiler compiler) { - this.compiler = compiler; + private Es6RewriteDestructuring(Builder builder) { + this.compiler = builder.compiler; + this.rewriteMode = builder.rewriteMode; + + switch (this.rewriteMode) { + case REWRITE_ALL_OBJECT_PATTERNS: + this.featuresToTriggerRunningPass = + FeatureSet.BARE_MINIMUM.with( + Feature.DEFAULT_PARAMETERS, Feature.DESTRUCTURING, Feature.ARRAY_PATTERN_REST); + + // If OBJECT_PATTERN_REST were to be present in featuresToTriggerRunningPass and not the + // input language featureSet (such as ES6=>ES5) the pass would be skipped. + this.featuresToMarkAsRemoved = + featuresToTriggerRunningPass.with(Feature.OBJECT_PATTERN_REST); + break; + case REWRITE_OBJECT_REST: + // TODO(bradfordcsmith): We shouldn't really need to remove default parameters for this + // case. + this.featuresToTriggerRunningPass = + FeatureSet.BARE_MINIMUM.with(Feature.DEFAULT_PARAMETERS, Feature.OBJECT_PATTERN_REST); + this.featuresToMarkAsRemoved = this.featuresToTriggerRunningPass; + break; + default: + throw new AssertionError( + "Es6RewriteDestructuring cannot handle ObjectDestructuringRewriteMode " + + this.rewriteMode); + } + } + + static class Builder { + + private final AbstractCompiler compiler; + private ObjectDestructuringRewriteMode rewriteMode = + ObjectDestructuringRewriteMode.REWRITE_ALL_OBJECT_PATTERNS; + + public Builder(AbstractCompiler compiler) { + this.compiler = compiler; + } + + public Builder setDestructuringRewriteMode(ObjectDestructuringRewriteMode rewriteMode) { + this.rewriteMode = rewriteMode; + return this; + } + + public Es6RewriteDestructuring build() { + return new Es6RewriteDestructuring(this); + } + } + + private static final class PatternNestingLevel { + + final Node pattern; + boolean hasNestedObjectRest; + + public PatternNestingLevel(Node pattern, boolean hasNestedRest) { + this.pattern = pattern; + this.hasNestedObjectRest = hasNestedRest; + } } @Override public void process(Node externs, Node root) { - TranspilationPasses.processTranspile(compiler, externs, transpiledFeatures, this); - TranspilationPasses.processTranspile(compiler, root, transpiledFeatures, this); - TranspilationPasses.maybeMarkFeaturesAsTranspiledAway(compiler, transpiledFeatures); + checkState(patternNestingStack.isEmpty()); + TranspilationPasses.processTranspile(compiler, externs, featuresToTriggerRunningPass, this); + TranspilationPasses.processTranspile(compiler, root, featuresToTriggerRunningPass, this); + TranspilationPasses.maybeMarkFeaturesAsTranspiledAway(compiler, featuresToMarkAsRemoved); + checkState(patternNestingStack.isEmpty()); } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { - TranspilationPasses.hotSwapTranspile(compiler, scriptRoot, transpiledFeatures, this); - TranspilationPasses.maybeMarkFeaturesAsTranspiledAway(compiler, transpiledFeatures); + checkState(patternNestingStack.isEmpty()); + TranspilationPasses.hotSwapTranspile(compiler, scriptRoot, featuresToTriggerRunningPass, this); + TranspilationPasses.maybeMarkFeaturesAsTranspiledAway(compiler, featuresToMarkAsRemoved); + checkState(patternNestingStack.isEmpty()); } @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { switch (n.getToken()) { case FUNCTION: - visitFunction(t, n); + ensureArrowFunctionsHaveBlockBodies(t, n); break; case PARAM_LIST: - visitParamList(n, parent); + pullDestructuringOutOfParams(n, parent); break; + case ARRAY_PATTERN: + case OBJECT_PATTERN: + { + boolean hasRest = n.isObjectPattern() && n.hasChildren() && n.getLastChild().isRest(); + if (!this.patternNestingStack.isEmpty() && hasRest) { + for (PatternNestingLevel level : patternNestingStack) { + if (level.hasNestedObjectRest) { + break; + } + level.hasNestedObjectRest = true; + } + this.patternNestingStack.peekLast().hasNestedObjectRest = true; + } + this.patternNestingStack.addLast(new PatternNestingLevel(n, hasRest)); + break; + } default: break; } @@ -83,6 +193,9 @@ public void visit(NodeTraversal t, Node n, Node parent) { case ARRAY_PATTERN: case OBJECT_PATTERN: visitPattern(t, n, parent); + if (n == this.patternNestingStack.getLast().pattern) { + this.patternNestingStack.removeLast(); + } break; default: break; @@ -92,7 +205,8 @@ public void visit(NodeTraversal t, Node n, Node parent) { /** * If the function is an arrow function, wrap the body in a block if it is not already a block. */ - private void visitFunction(NodeTraversal t, Node function) { + // TODO(mattmm): This should be separated from this pass. + private void ensureArrowFunctionsHaveBlockBodies(NodeTraversal t, Node function) { Node body = function.getLastChild(); if (!body.isBlock()) { body.detach(); @@ -102,10 +216,10 @@ private void visitFunction(NodeTraversal t, Node function) { } } - /** - * Processes trailing default and rest parameters. - */ - private void visitParamList(Node paramList, Node function) { + /** Processes trailing default and rest of function parameters. */ + // TODO(bradfordcsmith): Ideally if we're only removing OBJECT_REST, we should only do this when + // the parameter list contains a usage of OBJECT_REST. + private void pullDestructuringOutOfParams(Node paramList, Node function) { Node insertSpot = null; Node body = function.getLastChild(); int i = 0; @@ -227,7 +341,8 @@ private void visitPattern(NodeTraversal t, Node pattern, Node parent) { } else if (parent.isRest() || parent.isStringKey() || parent.isArrayPattern() - || parent.isDefaultValue()) { + || parent.isDefaultValue() + || parent.isComputedProp()) { // Nested pattern; do nothing. We will visit it after rewriting the parent. } else if (NodeUtil.isEnhancedFor(parent) || NodeUtil.isEnhancedFor(parent.getParent())) { visitDestructuringPatternInEnhancedFor(pattern); @@ -260,6 +375,24 @@ private void replacePattern( private void replaceObjectPattern( NodeTraversal t, Node objectPattern, Node rhs, Node parent, Node nodeToDetach) { String tempVarName = DESTRUCTURING_TEMP_VAR + (destructuringVarCounter++); + String restTempVarName = null; + // If the last child is a rest node we will want a list of the stated properties so we can + // exclude them from being written to the rest variable. + ArrayList propsToDeleteForRest = null; + if (objectPattern.hasChildren() && objectPattern.getLastChild().isRest()) { + propsToDeleteForRest = new ArrayList<>(); + restTempVarName = DESTRUCTURING_TEMP_VAR + destructuringVarCounter++; + } else if (rewriteMode == ObjectDestructuringRewriteMode.REWRITE_OBJECT_REST) { + // We are configured to only break object pattern rest, but this destructure has none. + if (!this.patternNestingStack.peekLast().hasNestedObjectRest) { + // Replacement is performed after the post-order visit has reached the root pattern node, so + // peeking last represents if there is a rest property anywhere in the entire pattern. All + // nesting levels of lower levels have already been popped. + destructuringVarCounter--; + return; + } + } + Node tempDecl = IR.var(IR.name(tempVarName), rhs.detach()) .useSourceInfoIfMissingFromForTree(objectPattern); // TODO(tbreisacher): Remove the "if" and add this JSDoc unconditionally. @@ -296,28 +429,64 @@ private void replaceObjectPattern( Node defaultValue = value.removeFirstChild(); newRHS = defaultValueHook(getprop, defaultValue); } + if (propsToDeleteForRest != null) { + Node propName = IR.string(child.getString()); + if (child.isQuotedString()) { + propName.setQuotedString(); + } + propsToDeleteForRest.add(propName); + } } else if (child.isComputedProp()) { - if (child.getLastChild().isDefaultValue()) { - newLHS = child.getLastChild().removeFirstChild(); - Node getelem = IR.getelem(IR.name(tempVarName), child.removeFirstChild()); - + boolean hasDefault = child.getLastChild().isDefaultValue(); + Node defaultNode = null; + Node defaultValue = null; + Node propExpr = child.removeFirstChild(); + if (hasDefault) { + defaultNode = child.getLastChild(); + newLHS = defaultNode.removeFirstChild(); + defaultValue = defaultNode.removeFirstChild(); + } else { + newLHS = child.removeFirstChild(); + } + if (propsToDeleteForRest != null) { + // A "...rest" variable is present and result of computation must be cached + String exprEvalTempVarName = DESTRUCTURING_TEMP_VAR + destructuringVarCounter++; + Node exprEvalDecl = IR.var(IR.name(exprEvalTempVarName), propExpr); + exprEvalDecl.useSourceInfoIfMissingFromForTree(child); + nodeToDetach.getParent().addChildBefore(exprEvalDecl, nodeToDetach); + propExpr = IR.name(exprEvalTempVarName); + propsToDeleteForRest.add(IR.name(exprEvalTempVarName)); + } + if (hasDefault) { + Node getelem = IR.getelem(IR.name(tempVarName), propExpr); String intermediateTempVarName = DESTRUCTURING_TEMP_VAR + (destructuringVarCounter++); Node intermediateDecl = IR.var(IR.name(intermediateTempVarName), getelem); intermediateDecl.useSourceInfoIfMissingFromForTree(child); nodeToDetach.getParent().addChildBefore(intermediateDecl, nodeToDetach); - - newRHS = - defaultValueHook( - IR.name(intermediateTempVarName), child.getLastChild().removeFirstChild()); + newRHS = defaultValueHook(IR.name(intermediateTempVarName), defaultValue); } else { - newRHS = IR.getelem(IR.name(tempVarName), child.removeFirstChild()); - newLHS = child.removeFirstChild(); + newRHS = IR.getelem(IR.name(tempVarName), propExpr); } } else if (child.isDefaultValue()) { newLHS = child.removeFirstChild(); Node defaultValue = child.removeFirstChild(); Node getprop = IR.getprop(IR.name(tempVarName), IR.string(newLHS.getString())); newRHS = defaultValueHook(getprop, defaultValue); + if (propsToDeleteForRest != null) { + propsToDeleteForRest.add(IR.stringKey(newLHS.getString())); + } + } else if (child.isRest()) { + if (next != null) { + throw new IllegalStateException("object rest may not be followed by any properties"); + } + Node assignCall = IR.call(NodeUtil.newQName(compiler, "Object.assign")); + assignCall.addChildToBack(IR.objectlit()); + assignCall.addChildToBack(IR.name(tempVarName)); + Node restTempDecl = IR.var(IR.name(restTempVarName), assignCall); + restTempDecl.useSourceInfoIfMissingFromForTree(objectPattern); + nodeToDetach.getParent().addChildAfter(restTempDecl, tempDecl); + newLHS = IR.name(child.getOnlyChild().getString()); + newRHS = objectPatternRestRHS(objectPattern, child, restTempVarName, propsToDeleteForRest); } else { throw new IllegalStateException("unexpected child"); } @@ -343,6 +512,52 @@ private void replaceObjectPattern( t.reportCodeChange(); } + /** + * Convert "rest" of object destructuring lhs by making a clone and deleting any properties that + * were stated in the original object pattern. + * + *

Nodes in statedProperties that are a stringKey will be used in a getprop when deleting. All + * other types will be used in a getelem such as what is done for computed properties. + * + *

+   *   {a, [foo()]:b, ...x} = rhs;
+   * becomes
+   *   var temp = rhs;
+   *   var temp1 = Object.assign({}, temp);
+   *   var temp2 = foo()
+   *   a = temp.a
+   *   b = temp[foo()]
+   *   x = (delete temp1.a, delete temp1[temp2], temp1);
+   * 
+ * + * @param rest node representing the "...rest" of objectPattern + * @param restTempVarName name of var containing clone of result of rhs evaluation + * @param statedProperties list of properties to delete from the clone + */ + private Node objectPatternRestRHS( + Node objectPattern, Node rest, String restTempVarName, ArrayList statedProperties) { + checkArgument(objectPattern.getLastChild() == rest); + Node result = IR.name(restTempVarName); + if (!statedProperties.isEmpty()) { + Iterator propItr = statedProperties.iterator(); + Node comma = deletionNodeForRestProperty(restTempVarName, propItr.next()); + while (propItr.hasNext()) { + comma = IR.comma(comma, deletionNodeForRestProperty(restTempVarName, propItr.next())); + } + result = IR.comma(comma, result); + } + result.useSourceInfoIfMissingFromForTree(rest); + return result; + } + + private Node deletionNodeForRestProperty(String restTempVarName, Node property) { + boolean useSquareBrackets = !property.isString() || property.isQuotedString(); + return new Node( + Token.DELPROP, + new Node( + useSquareBrackets ? Token.GETELEM : Token.GETPROP, IR.name(restTempVarName), property)); + } + /** * Convert
var [x, y] = rhs
 to:
    * 
@@ -353,6 +568,13 @@ private void replaceObjectPattern(
    */
   private void replaceArrayPattern(
       NodeTraversal t, Node arrayPattern, Node rhs, Node parent, Node nodeToDetach) {
+
+    if (rewriteMode == ObjectDestructuringRewriteMode.REWRITE_OBJECT_REST) {
+      if (patternNestingStack.isEmpty() || !patternNestingStack.peekLast().hasNestedObjectRest) {
+        return;
+      }
+    }
+
     String tempVarName = DESTRUCTURING_TEMP_VAR + (destructuringVarCounter++);
     Node tempDecl = IR.var(
         IR.name(tempVarName),
diff --git a/src/com/google/javascript/jscomp/EsNextToEs8Converter.java b/src/com/google/javascript/jscomp/EsNextToEs8Converter.java
index 178500b7221..a4ba1c1ad50 100644
--- a/src/com/google/javascript/jscomp/EsNextToEs8Converter.java
+++ b/src/com/google/javascript/jscomp/EsNextToEs8Converter.java
@@ -16,20 +16,15 @@
 package com.google.javascript.jscomp;
 
 import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
 import static com.google.javascript.jscomp.Es6ToEs3Util.createType;
 import static com.google.javascript.jscomp.Es6ToEs3Util.withType;
 
-import com.google.auto.value.AutoValue;
 import com.google.javascript.jscomp.parsing.parser.FeatureSet;
 import com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature;
 import com.google.javascript.rhino.IR;
 import com.google.javascript.rhino.Node;
-import com.google.javascript.rhino.Token;
 import com.google.javascript.rhino.jstype.JSType;
 import com.google.javascript.rhino.jstype.JSTypeNative;
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * Converts ESNext code to valid ES8 code.
@@ -41,14 +36,9 @@
 public final class EsNextToEs8Converter implements NodeTraversal.Callback, HotSwapCompilerPass {
   private final AbstractCompiler compiler;
   private static final FeatureSet transpiledFeatures =
-      FeatureSet.BARE_MINIMUM
-          .with(Feature.OBJECT_LITERALS_WITH_SPREAD)
-          .with(Feature.OBJECT_PATTERN_REST);
-  private final boolean addTypes;
-
-  private static final String PATTERN_TEMP_VAR = "$jscomp$objpattern$var";
+      FeatureSet.BARE_MINIMUM.with(Feature.OBJECT_LITERALS_WITH_SPREAD);
 
-  private int patternVarCounter = 0;
+  private final boolean addTypes;
 
   public EsNextToEs8Converter(AbstractCompiler compiler) {
     this.compiler = compiler;
@@ -79,11 +69,6 @@ public void visit(NodeTraversal t, Node n, Node parent) {
       case OBJECTLIT:
         visitObject(n);
         break;
-      case OBJECT_PATTERN:
-        if (n.hasChildren() && n.getLastChild().isRest()) {
-          visitObjectPatternWithRest(t, n, parent);
-        }
-        break;
       default:
         break;
     }
@@ -98,448 +83,6 @@ private void visitObject(Node obj) {
     }
   }
 
-  @AutoValue
-  abstract static class ComputedPropertyName {
-    static ComputedPropertyName create(String varName, Node computation) {
-      return new AutoValue_EsNextToEs8Converter_ComputedPropertyName(varName, computation);
-    }
-
-    abstract String varName();
-
-    abstract Node computation();
-  }
-
-  /*
-   * A Builder object that analyzes an object pattern and modifies the syntax trees accordingly.
-   *
-   * The constructor performs the analysis and stores any necessary information, but makes no change
-   * to the syntax trees.
-   *
-   * The insertBindings and prependDeclStatements methods effect the necessary modifications to the
-   * syntax tree.
-   */
-  private class ObjectPatternConverter {
-    // A trivial class mapping variable names to computations.
-
-    private final Node pattern;
-
-    // Name of the variable we will use to contain the result of the rhs
-    private final String rhsResultName;
-    // Name of the variable we will copy the rhs contents into before making deletions
-    private final String restDeletionVarName;
-
-    // Collect DELPROP nodes to delete the appropriate properties for the assignment to the rest
-    // variable.
-    private final List deletions = new ArrayList<>();
-    // Collect pairs of computed property calls and values.  In general, computed property
-    // computations may have side effects so we need to make sure they are called only once.  This
-    // object accomplishes this via an auxiliary temporary variable for each computed property.
-    private final List computedProperties = new ArrayList<>();
-
-    /*
-     * Constructs a right-hand side for the rest variable, using the deletions computed in the
-     * constructor.
-     */
-    private Node getRestRhs() {
-      // If no deletions are to be performed, default to the clone without any deletions.
-      Node restRhs = newName(this.restDeletionVarName);
-
-      if (!this.deletions.isEmpty()) {
-        Node comma = this.deletions.remove(0);
-        for (Node deletion : this.deletions) {
-          comma = IR.comma(comma, deletion);
-        }
-
-        // The "rest" variable should now be assigned the value of deletionVar
-        restRhs = IR.comma(comma, IR.name(this.restDeletionVarName));
-      }
-
-      restRhs.useSourceInfoIfMissingFromForTree(this.pattern);
-      return restRhs;
-    }
-
-    ObjectPatternConverter(Node pattern) {
-      this.pattern = pattern;
-      this.rhsResultName = PATTERN_TEMP_VAR + (patternVarCounter++);
-      this.restDeletionVarName = PATTERN_TEMP_VAR + (patternVarCounter++);
-
-      for (Node child : pattern.children()) {
-        if (child.isStringKey()) {
-          // Add a deletion with the name of the child.
-          deletions.add(
-              new Node(
-                  Token.DELPROP,
-                  new Node(
-                      child.isQuotedString() ? Token.GETELEM : Token.GETPROP,
-                      newName(this.restDeletionVarName),
-                      IR.string(child.getString()))));
-        } else if (child.isComputedProp()) {
-          // Create an auxiliary temp variable name.
-          String auxTempVarName = PATTERN_TEMP_VAR + (patternVarCounter++);
-          // Add a deletion with computed property using the auxiliary temp variable.
-          deletions.add(
-              new Node(
-                  Token.DELPROP,
-                  IR.getelem(newName(this.restDeletionVarName), IR.name(auxTempVarName))));
-
-          // Add a pair mapping the auxiliary temp variable to the property name computation.
-          ComputedPropertyName pair =
-              ComputedPropertyName.create(
-                  /* varName= */ auxTempVarName, /* computation= */ child.getFirstChild());
-
-          computedProperties.add(pair);
-        }
-      }
-    }
-
-    /*
-     * Wraps a call to IR.name with the temp var name and the appropriate source info.
-     */
-    Node newName(String nameString) {
-      Node name = IR.name(nameString);
-      name.useSourceInfoIfMissingFrom(this.pattern);
-      return name;
-    }
-
-    /**
-     * For a name F returns a Node equivalent to Object.assign({}, F).
-     * This permits deleting properties of F without deleting properties from the originally
-     * referenced object.
-     *
-     * The Node passed will be used in the AST, and must not exist anywhere else in the AST.
-     *
-     * @param from Node to copy
-     */
-    Node assignCopy(Node from) {
-      checkArgument(from.isName());
-
-      // TODO(mattmm): Only add types if the type checker passes have run already
-      JSType simpleObjectType =
-          createType(addTypes, compiler.getTypeRegistry(), JSTypeNative.EMPTY_OBJECT_LITERAL_TYPE);
-
-      Node call = IR.call(NodeUtil.newQName(compiler, "Object.assign"));
-      call.addChildToBack(withType(IR.objectlit(), simpleObjectType));
-      call.addChildToBack(from);
-
-      return call;
-    }
-
-    /*
-     * Inserts nodes into the grandparent of the pattern introducing the following series of
-     * bindings:
-     * (1) the temporary variable for this pattern to the thing the pattern was bound to
-     * (2) (if any) the auxiliary variables of the computed properties bound to their calls
-     * (3) the DESTRUCTURING_LHS containing the pattern itself, with the rest variable removed,
-     *     bound to the temporary variable
-     * (4) the rest variable bound to the temporary variable after deletion.
-     */
-    void insertBindings() {
-      Node parent = this.pattern.getParent();
-      checkState(parent.isDestructuringLhs(), parent);
-      Node grandparent = parent.getParent();
-      checkState(NodeUtil.isNameDeclaration(grandparent), grandparent);
-
-      // Add a binding for the temporary variable.
-      Node varName = this.newName(this.rhsResultName);
-      // The temp variable is bound to whatever the pattern was bound to.
-      varName.addChildToBack(this.pattern.getNext().detach());
-      // The temp variable binding goes before the DESTRUCTURING_LHS.
-      grandparent.addChildBefore(varName, parent);
-
-      for (ComputedPropertyName pair : this.computedProperties) {
-        // Replace the computation with the auxiliary temp variable name.
-        pair.computation().replaceWith(IR.name(pair.varName()));
-
-        Node compPropLhs = IR.name(pair.varName());
-        compPropLhs.addChildToBack(pair.computation());
-        compPropLhs.useSourceInfoIfMissingFromForTree(this.pattern);
-
-        // The auxiliary temp variable binding goes before the DESTRUCTURING_LHS.
-        grandparent.addChildBefore(compPropLhs, parent);
-      }
-
-      // Remove the rest variable from the pattern.
-      Node restNode = this.pattern.getLastChild().detach();
-
-      // The DESTRUCTURING_LHS is now bound to the temporary variable.
-      parent.addChildToBack(this.newName(this.rhsResultName));
-
-      Node delVarLhs = IR.name(this.restDeletionVarName);
-      Node delVarRhs = assignCopy(IR.name(this.rhsResultName));
-      delVarLhs.useSourceInfoIfMissingFromForTree(pattern);
-      delVarRhs.useSourceInfoIfMissingFromForTree(pattern);
-      delVarLhs.addChildToBack(delVarRhs);
-
-      // The temporary deletion var binding goes after the DESTRUCTURING_LHS.
-      grandparent.addChildAfter(delVarLhs, parent);
-
-      Node restLhs = restNode.removeFirstChild();
-      restLhs.addChildToBack(this.getRestRhs()); // get the temp variable after deletions.
-
-      // The rest binding goes after the deletion var.
-      grandparent.addChildAfter(restLhs, delVarLhs);
-    }
-
-    /*
-     * Prepends to the block introducing the following pair of statements:
-     * (1) (if any) let statements for auxiliary temp variables for computed properties in the
-     * pattern
-     * (2) A declaration (or assignment) for
-     *     (a) the head: the pattern without the rest, whose value is the temporary variable.
-     *     (b) the rest variable, whose value is the temporary variable after deletions.
-     */
-    void prependDeclStatements(NodeTraversal t, Token declType, Node block) {
-      List statements = new ArrayList<>();
-
-      for (ComputedPropertyName pair : this.computedProperties) {
-        // Replace the computation with the auxiliary temp variable name.
-        pair.computation().replaceWith(IR.name(pair.varName()));
-
-        Node let = IR.let(IR.name(pair.varName()), pair.computation());
-        let.useSourceInfoIfMissingFromForTree(this.pattern);
-        NodeUtil.addFeatureToScript(t.getCurrentFile(), Feature.LET_DECLARATIONS);
-
-        statements.add(let);
-      }
-
-      // Remove the pattern from its parent.
-      Node headLhs = this.pattern.detach();
-      Node headRhs = this.newName(this.rhsResultName);
-
-      // Remove the rest variable from the pattern.
-      Node restNode = this.pattern.getLastChild().detach();
-
-      Node restLhs = restNode.removeFirstChild();
-      Node restRhs = this.getRestRhs(); // get the temp variable after deletions.
-
-      Node delVarLhs = IR.name(this.restDeletionVarName);
-      Node delVarRhs = assignCopy(IR.name(this.rhsResultName));
-
-      if (declType == Token.ASSIGN) {
-        Node assign =
-            IR.exprResult(
-                IR.comma(
-                    IR.comma(
-                        // An assignment for the head.
-                        IR.assign(headLhs, headRhs),
-                        // An assignment for the temp var.
-                        IR.assign(delVarLhs, delVarRhs)),
-                    // An assignment for the rest.
-                    IR.assign(restLhs, restRhs)));
-        assign.useSourceInfoIfMissingFromForTree(this.pattern);
-        statements.add(assign);
-      } else {
-        // Create a declaration with the head.
-        Node decl = IR.declaration(headLhs, headRhs, declType);
-
-        // Add the declaration of the temporary deletion variable
-        delVarLhs.addChildToBack(delVarRhs);
-        decl.addChildToBack(delVarLhs);
-
-        // Add a second declaration for the rest.
-        restLhs.addChildToBack(restRhs);
-        decl.addChildToBack(restLhs);
-
-        decl.useSourceInfoIfMissingFromForTree(this.pattern);
-        statements.add(decl);
-      }
-
-      // Prepend the statements to the block.
-      Node next = block.getFirstChild();
-      for (Node statement : statements) {
-        if (next == null) {
-          block.addChildToBack(statement);
-        } else {
-          block.addChildBefore(statement, next);
-        }
-      }
-    }
-  }
-
-  /*
-   * Figure out whether the result of the node can be omitted.
-   */
-  private boolean canOmitResult(Node n) {
-    if (n.getParent().isExprResult()) {
-      // If the parent is an expression result the returned value is ignored.
-      return true;
-    }
-    if (n.getParent().isComma()) {
-      if (n.getNext() != null) {
-        // If the node is on the left side of a comma, its returned value is ignored.
-        return true;
-      } else {
-        // On the right side, it depends on the parent.
-        return canOmitResult(n.getParent());
-      }
-    }
-    // Err on the side of using an explicit return.
-    return false;
-  }
-
-  /*
-   * Handle object patterns with rest.
-   */
-  private void visitObjectPatternWithRest(NodeTraversal t, Node pattern, Node parent) {
-    checkArgument(pattern.isObjectPattern(), pattern);
-
-    // A Builder object that will effect necessary changes to the syntax tree.  The constructor
-    // makes no changes to the syntax tree, those will take place in subsequent calls to the
-    // ObjectPatternConverter object.
-    ObjectPatternConverter converter = new ObjectPatternConverter(pattern);
-
-    /*
-     * Convert 'try { x; } catch ({y, ...rest}) { z; }' to:
-     * 'try { x; } catch ($tmp) {
-     *    let {y} = $tmp,
-     *        rest = (delete $tmp.y, $tmp);
-     *    z;
-     *  }'
-     */
-    if (parent.isCatch()) {
-      // The handling block is the second child, after the catch.
-      Node block = parent.getSecondChild();
-
-      // Use let so that the variables have block scope.
-      converter.prependDeclStatements(t, Token.LET, block); // Detaches the pattern from its parent.
-      NodeUtil.addFeatureToScript(t.getCurrentFile(), Feature.LET_DECLARATIONS);
-
-      // Put the temp var in the catch, which was left empty by the removal of the pattern.
-      parent.addChildToFront(converter.newName(converter.rhsResultName));
-      compiler.reportChangeToEnclosingScope(parent);
-      return;
-    }
-
-    Node grandparent = parent.getParent();
-
-    /*
-     * Convert 'function f({x,...rest}) { z; }' to:
-     * 'function f($tmp) {
-     *    let {x} = $tmp,
-     *        rest = (delete $tmp.x, $tmp);
-     *    z;
-     *  }'
-     */
-    if (parent.isParamList() || (parent.isDefaultValue() && grandparent.isParamList())) {
-      // The function body is the Node after the param list.
-      Node body = parent.isParamList() ? parent.getNext() : grandparent.getNext();
-
-      // Use let so that the variables have function scope.
-      converter.prependDeclStatements(t, Token.LET, body); // Detaches the pattern from its parent.
-      NodeUtil.addFeatureToScript(t.getCurrentFile(), Feature.LET_DECLARATIONS);
-
-      // Put the temp var in the param list (or default), which was left empty by the removal of the
-      // pattern.
-      parent.addChildToFront(converter.newName(converter.rhsResultName));
-      compiler.reportChangeToEnclosingScope(parent);
-      return;
-    }
-
-    /*
-     * Convert 'for ({x, ...rest} of foo()) { z; }' to:
-     * 'for (let $tmp of foo()) {
-     *   ({x} = $tmp,
-     *    rest = (delete $tmp.x, $tmp));
-     *   z;
-     * }'
-     */
-    if (NodeUtil.isEnhancedFor(parent)) {
-      Node enhancedFor = parent;
-
-      Node block = enhancedFor.getLastChild();
-      converter.prependDeclStatements(t, Token.ASSIGN, block);
-
-      // Declare the deletion variable (will be assigned to later).
-      Node delVarDecl = IR.declaration(converter.newName(converter.restDeletionVarName), Token.LET);
-      delVarDecl.useSourceInfoIfMissingFromForTree(pattern);
-      block.addChildToFront(delVarDecl);
-
-      // Replace the pattern with a let for the temp variable.
-      Node let = new Node(Token.LET, converter.newName(converter.rhsResultName));
-      let.useSourceInfoIfMissingFrom(pattern);
-      enhancedFor.addChildToFront(let);
-
-      compiler.reportChangeToEnclosingScope(enhancedFor);
-      NodeUtil.addFeatureToScript(t.getCurrentFile(), Feature.LET_DECLARATIONS);
-    }
-
-    if (parent.isDestructuringLhs()) {
-      if (NodeUtil.isNameDeclaration(grandparent)) {
-        if (NodeUtil.isEnhancedFor(grandparent.getParent())) {
-          /*
-           * Convert 'for (var {x, ...rest} of foo()) { z; }' to:
-           * 'for (let $tmp of foo()) {
-           *   var {x} = $tmp,
-           *       rest = (delete $tmp.x, $tmp);
-           *   z;
-           * }'
-           * (also handles const and let).
-           */
-          Node enhancedFor = grandparent.getParent();
-
-          Node block = enhancedFor.getLastChild();
-          converter.prependDeclStatements(t, grandparent.getToken(), block);
-
-          // Replace the name declaration with a let for the temp variable.
-          Node let = new Node(Token.LET, converter.newName(converter.rhsResultName));
-          let.useSourceInfoIfMissingFrom(pattern);
-          enhancedFor.replaceChild(grandparent, let);
-
-          compiler.reportChangeToEnclosingScope(enhancedFor);
-          NodeUtil.addFeatureToScript(t.getCurrentFile(), Feature.LET_DECLARATIONS);
-          return;
-        } else {
-          /*
-           * Convert 'var ..., {x,...rest} = foo(), ...;' to
-           * 'var ..., $tmp=foo(), {x}=$tmp, rest=(delete $tmp.x, $tmp), ...;'
-           * (also handles const and let).
-           */
-          converter.insertBindings();
-          compiler.reportChangeToEnclosingScope(grandparent);
-        }
-      }
-    }
-
-    /*
-     * Convert '..., ... = {x,...rest} = foo(), ...' to
-     * '..., ... = (() => {
-     *   let $tmp = foo();
-     *   let $copy = $tmp; // copy is saved to be returned unmodified
-     *   {x} = $tmp, rest = (delete $tmp.x, $tmp);
-     *   return $copy;
-     * }(), ...'
-     * $copy is omitted if the return value is not needed.
-     */
-    if (parent.isAssign()) {
-      Node rhs = pattern.getNext();
-
-      Node body = IR.block();
-      converter.prependDeclStatements(t, Token.ASSIGN, body);
-      if (!canOmitResult(parent)) {
-        // If the result is needed then we have to store and return a pristine copy whose
-        // properties are not deleted. This value is stored in the resultVar.
-        // The return must be last.
-        body.addChildToBack(IR.returnNode(IR.name(converter.rhsResultName)));
-      }
-      // Declare the deletion variable (will be assigned to later).
-      body.addChildToFront(
-          IR.declaration(converter.newName(converter.restDeletionVarName), Token.LET));
-      // Add the new let for the temp variable at the beginning of the body.
-      body.addChildToFront(IR.let(converter.newName(converter.rhsResultName), rhs.detach()));
-      NodeUtil.addFeatureToScript(t.getCurrentFile(), Feature.LET_DECLARATIONS);
-
-      Node call = IR.call(IR.arrowFunction(IR.name(""), IR.paramList(), body));
-      NodeUtil.addFeatureToScript(t.getCurrentFile(), Feature.ARROW_FUNCTIONS);
-      call.putBooleanProp(Node.FREE_CALL, true);
-      call.useSourceInfoIfMissingFromForTree(pattern);
-      NodeUtil.markNewScopesChanged(call, compiler);
-
-      grandparent.replaceChild(parent, call);
-      compiler.reportChangeToEnclosingScope(grandparent);
-      return;
-    }
-  }
-
   /*
    * Convert '{first: b, c, ...spread, d: e, last}' to:
    *
diff --git a/src/com/google/javascript/jscomp/TranspilationPasses.java b/src/com/google/javascript/jscomp/TranspilationPasses.java
index 4fab9bc54b9..099f61fea96 100644
--- a/src/com/google/javascript/jscomp/TranspilationPasses.java
+++ b/src/com/google/javascript/jscomp/TranspilationPasses.java
@@ -24,6 +24,7 @@
 import static com.google.javascript.jscomp.parsing.parser.FeatureSet.ES8_MODULES;
 import static com.google.javascript.jscomp.parsing.parser.FeatureSet.ES_NEXT;
 
+import com.google.javascript.jscomp.Es6RewriteDestructuring.ObjectDestructuringRewriteMode;
 import com.google.javascript.jscomp.NodeTraversal.Callback;
 import com.google.javascript.jscomp.PassFactory.HotSwapPassFactory;
 import com.google.javascript.jscomp.parsing.parser.FeatureSet;
@@ -114,7 +115,8 @@ static void addPreTypecheckTranspilationPasses(
       passes.add(es6ConvertSuper);
       passes.add(es6RenameVariablesInParamLists);
       passes.add(es6SplitVariableDeclarations);
-      passes.add(es6RewriteDestructuring);
+      passes.add(
+          getEs6RewriteDestructuring(ObjectDestructuringRewriteMode.REWRITE_ALL_OBJECT_PATTERNS));
       passes.add(es6RewriteArrowFunction);
       passes.add(es6ExtractClasses);
       passes.add(es6RewriteClass);
@@ -125,6 +127,10 @@ static void addPreTypecheckTranspilationPasses(
         // TODO(b/73387406): Move each pass above here temporarily, then into
         // addEs6PostCheck Passes once the pass supports propagating type information
       }
+    } else if (options.needsTranspilationOf(Feature.OBJECT_PATTERN_REST)) {
+      passes.add(es6RenameVariablesInParamLists);
+      passes.add(es6SplitVariableDeclarations);
+      passes.add(getEs6RewriteDestructuring(ObjectDestructuringRewriteMode.REWRITE_OBJECT_REST));
     }
   }
 
@@ -198,7 +204,7 @@ protected HotSwapCompilerPass create(final AbstractCompiler compiler) {
 
         @Override
         protected FeatureSet featureSet() {
-          return ES8;
+          return ES2018;
         }
       };
 
@@ -237,7 +243,7 @@ protected HotSwapCompilerPass create(final AbstractCompiler compiler) {
 
         @Override
         protected FeatureSet featureSet() {
-          return ES8;
+          return ES2018;
         }
       };
 
@@ -250,7 +256,7 @@ protected HotSwapCompilerPass create(AbstractCompiler compiler) {
 
         @Override
         protected FeatureSet featureSet() {
-          return ES8;
+          return ES2018;
         }
       };
 
@@ -264,7 +270,7 @@ protected CompilerPass create(final AbstractCompiler compiler) {
 
         @Override
         protected FeatureSet featureSet() {
-          return ES8_MODULES;
+          return ES2018_MODULES;
         }
       };
 
@@ -294,18 +300,22 @@ protected FeatureSet featureSet() {
         }
       };
 
-  static final HotSwapPassFactory es6RewriteDestructuring =
-      new HotSwapPassFactory("Es6RewriteDestructuring") {
-        @Override
-        protected HotSwapCompilerPass create(final AbstractCompiler compiler) {
-          return new Es6RewriteDestructuring(compiler);
-        }
+  static final HotSwapPassFactory getEs6RewriteDestructuring(
+      ObjectDestructuringRewriteMode rewriteMode) {
+    return new HotSwapPassFactory("Es6RewriteDestructuring") {
+      @Override
+      protected HotSwapCompilerPass create(final AbstractCompiler compiler) {
+        return new Es6RewriteDestructuring.Builder(compiler)
+            .setDestructuringRewriteMode(rewriteMode)
+            .build();
+      }
 
-        @Override
-        protected FeatureSet featureSet() {
-          return ES8;
-        }
-      };
+      @Override
+      protected FeatureSet featureSet() {
+        return ES2018;
+      }
+    };
+  }
 
   static final HotSwapPassFactory es6RenameVariablesInParamLists =
       new HotSwapPassFactory("Es6RenameVariablesInParamLists") {
@@ -316,7 +326,7 @@ protected HotSwapCompilerPass create(final AbstractCompiler compiler) {
 
         @Override
         protected FeatureSet featureSet() {
-          return ES8;
+          return ES2018;
         }
       };
 
@@ -355,7 +365,7 @@ protected HotSwapCompilerPass create(final AbstractCompiler compiler) {
 
         @Override
         protected FeatureSet featureSet() {
-          return ES8;
+          return ES2018;
         }
       };
 
@@ -381,9 +391,9 @@ protected HotSwapCompilerPass create(final AbstractCompiler compiler) {
 
         @Override
         protected FeatureSet featureSet() {
-          return ES8;
+          return ES2018;
         }
-  };
+      };
 
   /** Injects runtime library code needed for transpiled ES6 code. */
   static final HotSwapPassFactory es6InjectRuntimeLibraries =
diff --git a/test/com/google/javascript/jscomp/Es6RewriteDestructuringTest.java b/test/com/google/javascript/jscomp/Es6RewriteDestructuringTest.java
index bca66dc2263..66e98a5c64d 100644
--- a/test/com/google/javascript/jscomp/Es6RewriteDestructuringTest.java
+++ b/test/com/google/javascript/jscomp/Es6RewriteDestructuringTest.java
@@ -18,17 +18,27 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
+import com.google.javascript.jscomp.Es6RewriteDestructuring.ObjectDestructuringRewriteMode;
 
 public class Es6RewriteDestructuringTest extends CompilerTestCase {
 
+  private ObjectDestructuringRewriteMode destructuringRewriteMode =
+      ObjectDestructuringRewriteMode.REWRITE_ALL_OBJECT_PATTERNS;
+
   @Override
   protected void setUp() throws Exception {
     super.setUp();
-    setAcceptedLanguage(LanguageMode.ECMASCRIPT_2015);
+    setAcceptedLanguage(LanguageMode.ECMASCRIPT_2018);
     disableTypeCheck();
     enableRunTypeCheckAfterProcessing();
   }
 
+  @Override
+  protected void tearDown() throws Exception {
+    super.tearDown();
+    this.destructuringRewriteMode = ObjectDestructuringRewriteMode.REWRITE_ALL_OBJECT_PATTERNS;
+  }
+
   @Override
   protected int getNumRepetitions() {
     return 1;
@@ -43,7 +53,9 @@ protected CompilerOptions getOptions() {
 
   @Override
   protected CompilerPass getProcessor(Compiler compiler) {
-    return new Es6RewriteDestructuring(compiler);
+    return new Es6RewriteDestructuring.Builder(compiler)
+        .setDestructuringRewriteMode(destructuringRewriteMode)
+        .build();
   }
 
   public void testObjectDestructuring() {
@@ -821,6 +833,557 @@ public void testTryCatch() {
             "}"));
   }
 
+  public void testObjectPatternWithRestDecl() {
+    test(
+        "var {a: b, c: d, ...rest} = foo();",
+        lines(
+            "var $jscomp$destructuring$var0 = foo();",
+            "var $jscomp$destructuring$var1 = Object.assign({}, $jscomp$destructuring$var0);",
+            "var b = $jscomp$destructuring$var0.a;",
+            "var d = $jscomp$destructuring$var0.c;",
+            "var rest = (delete $jscomp$destructuring$var1.a,",
+            "            delete $jscomp$destructuring$var1.c,",
+            "            $jscomp$destructuring$var1);"));
+
+    test(
+        "const {a: b, c: d, ...rest} = foo();",
+        lines(
+            "/** @const */ var $jscomp$destructuring$var0 = foo();",
+            "var $jscomp$destructuring$var1 = Object.assign({}, $jscomp$destructuring$var0);",
+            "const b = $jscomp$destructuring$var0.a;",
+            "const d = $jscomp$destructuring$var0.c;",
+            "const rest = (delete $jscomp$destructuring$var1.a,",
+            "              delete $jscomp$destructuring$var1.c,",
+            "              $jscomp$destructuring$var1);"));
+
+    test(
+        "let {a: b, c: d, ...rest} = foo();",
+        lines(
+            "var $jscomp$destructuring$var0 = foo();",
+            "var $jscomp$destructuring$var1 = Object.assign({}, $jscomp$destructuring$var0);",
+            "let b = $jscomp$destructuring$var0.a;",
+            "let d = $jscomp$destructuring$var0.c;",
+            "let rest = (delete $jscomp$destructuring$var1.a,",
+            "            delete $jscomp$destructuring$var1.c,",
+            "            $jscomp$destructuring$var1);"));
+
+    test(
+        "var pre = foo(); var {a: b, c: d, ...rest} = foo();",
+        lines(
+            "var pre = foo();",
+            "var $jscomp$destructuring$var0 = foo();",
+            "var $jscomp$destructuring$var1 = Object.assign({}, $jscomp$destructuring$var0);",
+            "var b = $jscomp$destructuring$var0.a;",
+            "var d = $jscomp$destructuring$var0.c;",
+            "var rest = (delete $jscomp$destructuring$var1.a,",
+            "            delete $jscomp$destructuring$var1.c,",
+            "            $jscomp$destructuring$var1);"));
+
+    test(
+        "var {a: b, c: d, ...rest} = foo(); var post = foo();",
+        lines(
+            "var $jscomp$destructuring$var0 = foo();",
+            "var $jscomp$destructuring$var1 = Object.assign({}, $jscomp$destructuring$var0);",
+            "var b = $jscomp$destructuring$var0.a;",
+            "var d = $jscomp$destructuring$var0.c;",
+            "var rest = (delete $jscomp$destructuring$var1.a,",
+            "            delete $jscomp$destructuring$var1.c,",
+            "            $jscomp$destructuring$var1);",
+            "var post = foo();"));
+
+    test(
+        "var pre = foo(); var {a: b, c: d, ...rest} = foo(); var post = foo();",
+        lines(
+            "var pre = foo();",
+            "var $jscomp$destructuring$var0 = foo();",
+            "var $jscomp$destructuring$var1 = Object.assign({}, $jscomp$destructuring$var0);",
+            "var b = $jscomp$destructuring$var0.a;",
+            "var d = $jscomp$destructuring$var0.c;",
+            "var rest = (delete $jscomp$destructuring$var1.a,",
+            "            delete $jscomp$destructuring$var1.c,",
+            "            $jscomp$destructuring$var1);",
+            "var post = foo();"));
+
+    test(
+        "var {a: b1, c: d1, ...rest1} = foo(); var {a: b2, c: d2, ...rest2} = foo();",
+        lines(
+            "var $jscomp$destructuring$var0 = foo();",
+            "var $jscomp$destructuring$var1 = Object.assign({}, $jscomp$destructuring$var0);",
+            "var b1 = $jscomp$destructuring$var0.a;",
+            "var d1 = $jscomp$destructuring$var0.c;",
+            "var rest1 = (delete $jscomp$destructuring$var1.a,",
+            "             delete $jscomp$destructuring$var1.c,",
+            "             $jscomp$destructuring$var1);",
+            "var $jscomp$destructuring$var2 = foo();",
+            "var $jscomp$destructuring$var3 = Object.assign({}, $jscomp$destructuring$var2);",
+            "var b2 = $jscomp$destructuring$var2.a;",
+            "var d2 = $jscomp$destructuring$var2.c;",
+            "var rest2 = (delete $jscomp$destructuring$var3.a,",
+            "             delete $jscomp$destructuring$var3.c,",
+            "             $jscomp$destructuring$var3);"));
+
+    test(
+        "var {...rest} = foo();",
+        lines(
+            "var $jscomp$destructuring$var0 = foo();",
+            "var $jscomp$destructuring$var1 = Object.assign({}, $jscomp$destructuring$var0);",
+            "var rest = ($jscomp$destructuring$var1);"));
+
+    test(
+        "const {...rest} = foo();",
+        lines(
+            "/** @const */ var $jscomp$destructuring$var0 = foo();",
+            "var $jscomp$destructuring$var1 = Object.assign({}, $jscomp$destructuring$var0);",
+            "const rest = ($jscomp$destructuring$var1);"));
+  }
+
+  public void testObjectPatternWithRestAssignStatement() {
+    test(
+        "var b,d,rest; ({a: b, c: d, ...rest} = foo());",
+        lines(
+            "var b,d,rest;",
+            "var $jscomp$destructuring$var0 = foo();",
+            "var $jscomp$destructuring$var1 = Object.assign({}, $jscomp$destructuring$var0);",
+            "b = $jscomp$destructuring$var0.a;",
+            "d = $jscomp$destructuring$var0.c;",
+            "rest = (delete $jscomp$destructuring$var1.a,",
+            "            delete $jscomp$destructuring$var1.c,",
+            "            $jscomp$destructuring$var1);"));
+
+    test(
+        "var b,d,rest,pre; pre = foo(), {a: b, c: d, ...rest} = foo();",
+        lines(
+            "var b,d,rest,pre;",
+            "pre = foo(),",
+            "      (() => {",
+            "        let $jscomp$destructuring$var0 = foo();",
+            "        var $jscomp$destructuring$var1 = $jscomp$destructuring$var0;",
+            "        var $jscomp$destructuring$var2=Object.assign({},$jscomp$destructuring$var1);",
+            "        b = $jscomp$destructuring$var1.a;",
+            "        d = $jscomp$destructuring$var1.c;",
+            "        rest = (delete $jscomp$destructuring$var2.a,",
+            "                delete $jscomp$destructuring$var2.c,",
+            "                $jscomp$destructuring$var2);",
+            "        return $jscomp$destructuring$var0",
+            "      })();"));
+
+    test(
+        "var b,d,rest,post; ({a: b, c: d, ...rest} = foo()), post = foo();",
+        lines(
+            "var b,d,rest,post;",
+            "(() => {",
+            "  let $jscomp$destructuring$var0 = foo();",
+            "  var $jscomp$destructuring$var1 = $jscomp$destructuring$var0;",
+            "  var $jscomp$destructuring$var2 = Object.assign({},$jscomp$destructuring$var1);",
+            "  b = $jscomp$destructuring$var1.a;",
+            "  d = $jscomp$destructuring$var1.c;",
+            "  rest = (delete $jscomp$destructuring$var2.a,",
+            "          delete $jscomp$destructuring$var2.c,",
+            "          $jscomp$destructuring$var2);",
+            "  return $jscomp$destructuring$var0",
+            "})(), post = foo();"));
+
+    test(
+        "var b,d,rest,pre,post; pre = foo(), {a: b, c: d, ...rest} = foo(), post = foo();",
+        lines(
+            "var b,d,rest,pre,post;",
+            "pre = foo(),",
+            "      (() => {",
+            "        let $jscomp$destructuring$var0 = foo();",
+            "        var $jscomp$destructuring$var1 = $jscomp$destructuring$var0;",
+            "        var $jscomp$destructuring$var2=Object.assign({},$jscomp$destructuring$var1);",
+            "        b = $jscomp$destructuring$var1.a;",
+            "        d = $jscomp$destructuring$var1.c;",
+            "        rest = (delete $jscomp$destructuring$var2.a,",
+            "                delete $jscomp$destructuring$var2.c,",
+            "                $jscomp$destructuring$var2);",
+            "        return $jscomp$destructuring$var0",
+            "      })(),",
+            "      post = foo();"));
+
+    test(
+        lines(
+            "var b1,d1,rest1,b2,d2,rest2;",
+            "({a: b1, c: d1, ...rest1} = foo(),",
+            " {a: b2, c: d2, ...rest2} = foo());"),
+        lines(
+            "var b1,d1,rest1,b2,d2,rest2;",
+            "      (() => {",
+            "        let $jscomp$destructuring$var0 = foo();",
+            "        var $jscomp$destructuring$var1 = $jscomp$destructuring$var0;",
+            "        var $jscomp$destructuring$var2=Object.assign({},$jscomp$destructuring$var1);",
+            "        b1 = $jscomp$destructuring$var1.a;",
+            "        d1 = $jscomp$destructuring$var1.c;",
+            "        rest1 = (delete $jscomp$destructuring$var2.a,",
+            "                 delete $jscomp$destructuring$var2.c,",
+            "                 $jscomp$destructuring$var2);",
+            "        return $jscomp$destructuring$var0",
+            "      })(),",
+            "      (() => {",
+            "        let $jscomp$destructuring$var3 = foo();",
+            "        var $jscomp$destructuring$var4 = $jscomp$destructuring$var3;",
+            "        var $jscomp$destructuring$var5=Object.assign({},$jscomp$destructuring$var4);",
+            "        b2 = $jscomp$destructuring$var4.a;",
+            "        d2 = $jscomp$destructuring$var4.c;",
+            "        rest2 = (delete $jscomp$destructuring$var5.a,",
+            "                 delete $jscomp$destructuring$var5.c,",
+            "                 $jscomp$destructuring$var5);",
+            "        return $jscomp$destructuring$var3",
+            "      })();"));
+  }
+
+  public void testObjectPatternWithRestAssignExpr() {
+    test(
+        "var x,b,d,rest; x = ({a: b, c: d, ...rest} = foo());",
+        lines(
+            "var x,b,d,rest;",
+            "x = (()=>{",
+            "    let $jscomp$destructuring$var0 = foo();",
+            "    var $jscomp$destructuring$var1 = $jscomp$destructuring$var0;",
+            "    var $jscomp$destructuring$var2 = Object.assign({}, $jscomp$destructuring$var1);",
+            "    b = $jscomp$destructuring$var1.a;",
+            "    d = $jscomp$destructuring$var1.c;",
+            "    rest = (delete $jscomp$destructuring$var2.a,",
+            "            delete $jscomp$destructuring$var2.c,",
+            "            $jscomp$destructuring$var2);",
+            "    return $jscomp$destructuring$var0",
+            "})();"));
+
+    test(
+        "var x,b,d,rest; baz({a: b, c: d, ...rest} = foo());",
+        lines(
+            "var x,b,d,rest;",
+            "baz((()=>{",
+            "    let $jscomp$destructuring$var0 = foo();",
+            "    var $jscomp$destructuring$var1 = $jscomp$destructuring$var0;",
+            "    var $jscomp$destructuring$var2 = Object.assign({}, $jscomp$destructuring$var1);",
+            "    b = $jscomp$destructuring$var1.a;",
+            "    d = $jscomp$destructuring$var1.c;",
+            "    rest = (delete $jscomp$destructuring$var2.a,",
+            "            delete $jscomp$destructuring$var2.c,",
+            "            $jscomp$destructuring$var2);",
+            "    return $jscomp$destructuring$var0;",
+            "})());"));
+  }
+
+  public void testObjectPatternWithRestForOf() {
+    test(
+        "for ({a: b, c: d, ...rest} of foo()) { console.log(rest.z); }",
+        lines(
+            "for (var $jscomp$destructuring$var0 of foo()) {",
+            "    var $jscomp$destructuring$var1 = $jscomp$destructuring$var0;",
+            "    var $jscomp$destructuring$var2 = Object.assign({}, $jscomp$destructuring$var1);",
+            "    b = $jscomp$destructuring$var1.a;",
+            "    d = $jscomp$destructuring$var1.c;",
+            "    rest = (delete $jscomp$destructuring$var2.a,",
+            "            delete $jscomp$destructuring$var2.c,",
+            "            $jscomp$destructuring$var2);",
+            "    console.log(rest.z);",
+            "}"));
+
+    test(
+        "for (var {a: b, c: d, ...rest} of foo()) { console.log(rest.z); }",
+        lines(
+            "for (var $jscomp$destructuring$var0 of foo()) {",
+            "    var $jscomp$destructuring$var1 = $jscomp$destructuring$var0;",
+            "    var $jscomp$destructuring$var2 = Object.assign({}, $jscomp$destructuring$var1);",
+            "    var b = $jscomp$destructuring$var1.a;",
+            "    var d = $jscomp$destructuring$var1.c;",
+            "    var rest = (delete $jscomp$destructuring$var2.a,",
+            "            delete $jscomp$destructuring$var2.c,",
+            "            $jscomp$destructuring$var2);",
+            "    console.log(rest.z);",
+            "}"));
+
+    test(
+        "for (let {a: b, c: d, ...rest} of foo()) { console.log(rest.z); }",
+        lines(
+            "for (let $jscomp$destructuring$var0 of foo()) {",
+            "    var $jscomp$destructuring$var1 = $jscomp$destructuring$var0;",
+            "    var $jscomp$destructuring$var2 = Object.assign({}, $jscomp$destructuring$var1);",
+            "    let b = $jscomp$destructuring$var1.a;",
+            "    let d = $jscomp$destructuring$var1.c;",
+            "    let rest = (delete $jscomp$destructuring$var2.a,",
+            "            delete $jscomp$destructuring$var2.c,",
+            "            $jscomp$destructuring$var2);",
+            "    console.log(rest.z);",
+            "}"));
+
+    test(
+        "for (const {a: b, c: d, ...rest} of foo()) { console.log(rest.z); }",
+        lines(
+            "for (const $jscomp$destructuring$var0 of foo()) {",
+            "    /** @const */ var $jscomp$destructuring$var1 = $jscomp$destructuring$var0;",
+            "    var $jscomp$destructuring$var2 = Object.assign({}, $jscomp$destructuring$var1);",
+            "    const b = $jscomp$destructuring$var1.a;",
+            "    const d = $jscomp$destructuring$var1.c;",
+            "    const rest = (delete $jscomp$destructuring$var2.a,",
+            "            delete $jscomp$destructuring$var2.c,",
+            "            $jscomp$destructuring$var2);",
+            "    console.log(rest.z);",
+            "}"));
+
+    test(
+        "for (var {a: b, [baz()]: d, ...rest} of foo()) { console.log(rest.z); }",
+        lines(
+            "for (var $jscomp$destructuring$var0 of foo()) {",
+            "    var $jscomp$destructuring$var1 = $jscomp$destructuring$var0;",
+            "    var $jscomp$destructuring$var2 = Object.assign({}, $jscomp$destructuring$var1);",
+            "    var b = $jscomp$destructuring$var1.a;",
+            "    var $jscomp$destructuring$var3 = baz();",
+            "    var d = $jscomp$destructuring$var1[$jscomp$destructuring$var3];",
+            "    var rest = (delete $jscomp$destructuring$var2.a,",
+            "            delete $jscomp$destructuring$var2[$jscomp$destructuring$var3],",
+            "            $jscomp$destructuring$var2);",
+            "    console.log(rest.z);",
+            "}"));
+
+    test(
+        "for (var {a: b, [baz()]: d = 1, ...rest} of foo()) { console.log(rest.z); }",
+        lines(
+            "for (var $jscomp$destructuring$var0 of foo()) {",
+            "    var $jscomp$destructuring$var1 = $jscomp$destructuring$var0;",
+            "    var $jscomp$destructuring$var2 = Object.assign({}, $jscomp$destructuring$var1);",
+            "    var b = $jscomp$destructuring$var1.a;",
+            "    var $jscomp$destructuring$var3 = baz();",
+            "    var $jscomp$destructuring$var4 = ",
+            "        $jscomp$destructuring$var1[$jscomp$destructuring$var3];",
+            "    var d = $jscomp$destructuring$var4===undefined ? 1 : $jscomp$destructuring$var4;",
+            "    var rest = (delete $jscomp$destructuring$var2.a,",
+            "            delete $jscomp$destructuring$var2[$jscomp$destructuring$var3],",
+            "            $jscomp$destructuring$var2);",
+            "    console.log(rest.z);",
+            "}"));
+  }
+
+  public void testObjectPatternWithRestAndComputedPropertyName() {
+    test(
+        "var {a: b = 3, [bar()]: d, [baz()]: e, ...rest} = foo();",
+        lines(
+            "var $jscomp$destructuring$var0 = foo();",
+            "var $jscomp$destructuring$var1 = Object.assign({},$jscomp$destructuring$var0);",
+            "var b = $jscomp$destructuring$var0.a===undefined ? 3 : $jscomp$destructuring$var0.a;",
+            "var $jscomp$destructuring$var2 = bar();",
+            "var d = $jscomp$destructuring$var0[$jscomp$destructuring$var2];",
+            "var $jscomp$destructuring$var3 = baz();",
+            "var e = $jscomp$destructuring$var0[$jscomp$destructuring$var3];",
+            "var rest = (delete $jscomp$destructuring$var1.a,",
+            "            delete $jscomp$destructuring$var1[$jscomp$destructuring$var2],",
+            "            delete $jscomp$destructuring$var1[$jscomp$destructuring$var3],",
+            "            $jscomp$destructuring$var1);"));
+  }
+
+  public void testObjectPatternWithRestAndDefaults() {
+    test(
+        "var {a = 3, ...rest} = foo();",
+        lines(
+            "var $jscomp$destructuring$var0 = foo();",
+            "var $jscomp$destructuring$var1 = Object.assign({}, $jscomp$destructuring$var0);",
+            "var a = $jscomp$destructuring$var0.a===undefined ? 3 : $jscomp$destructuring$var0.a;",
+            "var rest = (delete $jscomp$destructuring$var1.a,",
+            "            $jscomp$destructuring$var1);"));
+
+    test(
+        "var {[bar()]:a = 3, 'b c':b = 12, ...rest} = foo();",
+        lines(
+            "var $jscomp$destructuring$var0=foo();",
+            "var $jscomp$destructuring$var1 = Object.assign({},$jscomp$destructuring$var0);",
+            "var $jscomp$destructuring$var2 = bar();",
+            "var $jscomp$destructuring$var3 =",
+            "    $jscomp$destructuring$var0[$jscomp$destructuring$var2];",
+            "var a = $jscomp$destructuring$var3===undefined ? 3 : $jscomp$destructuring$var3;",
+            "var b = $jscomp$destructuring$var0[\"b c\"]===undefined",
+            "    ? 12 : $jscomp$destructuring$var0[\"b c\"];",
+            "var rest=(delete $jscomp$destructuring$var1[$jscomp$destructuring$var2],",
+            "          delete $jscomp$destructuring$var1[\"b c\"],",
+            "          $jscomp$destructuring$var1);"));
+  }
+
+  public void testObjectPatternWithRestInCatch() {
+    test(
+        "try {} catch ({first, second, ...rest}) { console.log(rest.z); }",
+        lines(
+            "try {}",
+            "catch ($jscomp$destructuring$var0) {",
+            "  var $jscomp$destructuring$var1 = $jscomp$destructuring$var0;",
+            "  var $jscomp$destructuring$var2 = Object.assign({}, $jscomp$destructuring$var1);",
+            "  let first = $jscomp$destructuring$var1.first;",
+            "  let second = $jscomp$destructuring$var1.second;",
+            "  let rest = (delete $jscomp$destructuring$var2.first, ",
+            "              delete $jscomp$destructuring$var2.second, ",
+            "              $jscomp$destructuring$var2);",
+            "  console.log(rest.z);",
+            "}"));
+  }
+
+  public void testObjectPatternWithRestAssignReturn() {
+    test(
+        "function f() { return {x:a, ...rest} = foo(); }",
+        lines(
+            "function f() {",
+            "  return (() => {",
+            "    let $jscomp$destructuring$var0 = foo();",
+            "    var $jscomp$destructuring$var1 = $jscomp$destructuring$var0;",
+            "    var $jscomp$destructuring$var2 = Object.assign({}, $jscomp$destructuring$var1);",
+            "    a = $jscomp$destructuring$var1.x;",
+            "    rest = (delete $jscomp$destructuring$var2.x,",
+            "            $jscomp$destructuring$var2);",
+            "    return $jscomp$destructuring$var0",
+            "  })();",
+            "}"));
+  }
+
+  public void testObjectPatternWithRestParamList() {
+    test(
+        "function f({x = a(), ...rest}, y=b()) { console.log(y); }",
+        lines(
+            "function f($jscomp$destructuring$var0,y) {",
+            "  var $jscomp$destructuring$var1 = $jscomp$destructuring$var0;",
+            "  var $jscomp$destructuring$var2 = Object.assign({},$jscomp$destructuring$var1);",
+            "  var x = $jscomp$destructuring$var1.x===undefined",
+            "      ? a() : $jscomp$destructuring$var1.x;",
+            "  var rest= (delete $jscomp$destructuring$var2.x,",
+            "             $jscomp$destructuring$var2);",
+            "  y = y===undefined ? b() : y;",
+            "  console.log(y)",
+            "}"));
+
+    test(
+        "function f({x = a(), ...rest}={}, y=b()) { console.log(y); }",
+        lines(
+            "function f($jscomp$destructuring$var0,y) {",
+            "  var $jscomp$destructuring$var1 = $jscomp$destructuring$var0===undefined",
+            "      ? {} : $jscomp$destructuring$var0;",
+            "  var $jscomp$destructuring$var2 = Object.assign({},$jscomp$destructuring$var1);",
+            "  var x = $jscomp$destructuring$var1.x===undefined",
+            "      ? a() : $jscomp$destructuring$var1.x;",
+            "  var rest= (delete $jscomp$destructuring$var2.x,",
+            "             $jscomp$destructuring$var2);",
+            "  y = y===undefined ? b() : y;",
+            "  console.log(y)",
+            "}"));
+  }
+
+  public void testObjectPatternWithRestArrowParamList() {
+    test(
+        "var f = ({x = a(), ...rest}, y=b()) => { console.log(y); };",
+        lines(
+            "var f = ($jscomp$destructuring$var0,y) => {",
+            "  var $jscomp$destructuring$var1 = $jscomp$destructuring$var0;",
+            "  var $jscomp$destructuring$var2 = Object.assign({},$jscomp$destructuring$var1);",
+            "  var x = $jscomp$destructuring$var1.x===undefined",
+            "      ? a() : $jscomp$destructuring$var1.x;",
+            "  var rest = (delete $jscomp$destructuring$var2.x,",
+            "              $jscomp$destructuring$var2);",
+            "  y = y===undefined ? b() : y;",
+            "  console.log(y)",
+            "}"));
+
+    test(
+        "var f = ({x = a(), ...rest}={}, y=b()) => { console.log(y); };",
+        lines(
+            "var f = ($jscomp$destructuring$var0,y) => {",
+            "  var $jscomp$destructuring$var1 = $jscomp$destructuring$var0===undefined",
+            "      ? {} : $jscomp$destructuring$var0;",
+            "  var $jscomp$destructuring$var2 = Object.assign({},$jscomp$destructuring$var1);",
+            "  var x = $jscomp$destructuring$var1.x===undefined",
+            "      ? a() : $jscomp$destructuring$var1.x;",
+            "  var rest= (delete $jscomp$destructuring$var2.x,",
+            "             $jscomp$destructuring$var2);",
+            "  y = y===undefined ? b() : y;",
+            "  console.log(y)",
+            "}"));
+  }
+
+  public void testAllRewriteMode() {
+    this.destructuringRewriteMode = ObjectDestructuringRewriteMode.REWRITE_ALL_OBJECT_PATTERNS;
+
+    test(
+        "var {a} = foo();",
+        lines("var $jscomp$destructuring$var0 = foo();", "var a = $jscomp$destructuring$var0.a;"));
+
+    test(
+        "var {a} = foo(); var {...b} = bar();",
+        lines(
+            "var $jscomp$destructuring$var0 = foo();",
+            "var a = $jscomp$destructuring$var0.a;",
+            "var $jscomp$destructuring$var1 = bar();",
+            "var $jscomp$destructuring$var2 = Object.assign({}, $jscomp$destructuring$var1);",
+            "var b = $jscomp$destructuring$var2;"));
+
+    test(
+        "var {[foo0()]: {[foo1()]: a, ...r}, [foo2()]: { [foo3()]: b}, [foo4()]: c } = bar();",
+        lines(
+            "var $jscomp$destructuring$var0 = bar();",
+            "var $jscomp$destructuring$var1 = $jscomp$destructuring$var0[foo0()];",
+            "var $jscomp$destructuring$var2 = Object.assign({},$jscomp$destructuring$var1);",
+            "var $jscomp$destructuring$var3 = foo1();",
+            "var a = $jscomp$destructuring$var1[$jscomp$destructuring$var3];",
+            "var r = (delete $jscomp$destructuring$var2[$jscomp$destructuring$var3],",
+            "         $jscomp$destructuring$var2);",
+            "var $jscomp$destructuring$var4 = $jscomp$destructuring$var0[foo2()];",
+            "var b = $jscomp$destructuring$var4[foo3()];",
+            "var c = $jscomp$destructuring$var0[foo4()]"));
+
+    test(
+        "var [a] = foo(); var [{b}] = foo(); var [{...c}] = foo();",
+        lines(
+            // var [a] = foo();
+            "var $jscomp$destructuring$var0 = $jscomp.makeIterator(foo());",
+            "var a = $jscomp$destructuring$var0.next().value;",
+            // var [{b}] = foo();
+            "var $jscomp$destructuring$var1 = $jscomp.makeIterator(foo());",
+            "var $jscomp$destructuring$var2 = $jscomp$destructuring$var1.next().value;",
+            "var b = $jscomp$destructuring$var2.b;",
+            // var [{...c}] = foo();
+            "var $jscomp$destructuring$var3 = $jscomp.makeIterator(foo());",
+            "var $jscomp$destructuring$var4 = $jscomp$destructuring$var3.next().value;",
+            "var $jscomp$destructuring$var5 = Object.assign({},$jscomp$destructuring$var4);",
+            "var c = $jscomp$destructuring$var5"));
+  }
+
+  public void testOnlyRestRewriteMode() {
+    this.destructuringRewriteMode = ObjectDestructuringRewriteMode.REWRITE_OBJECT_REST;
+
+    test("var {a} = foo();", "var {a} = foo();");
+
+    test(
+        "var {a} = foo(); var {...b} = bar();",
+        lines(
+            "var {a} = foo();",
+            "var $jscomp$destructuring$var0 = bar();",
+            "var $jscomp$destructuring$var1 = Object.assign({}, $jscomp$destructuring$var0);",
+            "var b = $jscomp$destructuring$var1;"));
+
+    // test that object patterns are rewritten if they have a rest property nested within
+    test(
+        "var {[foo0()]: {[foo1()]: a, ...r}, [foo2()]: { [foo3()]: b}, [foo4()]: c } = bar();",
+        lines(
+            "var $jscomp$destructuring$var0 = bar();",
+            "var $jscomp$destructuring$var1 = $jscomp$destructuring$var0[foo0()];",
+            "var $jscomp$destructuring$var2 = Object.assign({},$jscomp$destructuring$var1);",
+            "var $jscomp$destructuring$var3 = foo1();",
+            "var a = $jscomp$destructuring$var1[$jscomp$destructuring$var3];",
+            "var r = (delete $jscomp$destructuring$var2[$jscomp$destructuring$var3],",
+            "         $jscomp$destructuring$var2);",
+            "var $jscomp$destructuring$var4 = $jscomp$destructuring$var0[foo2()];",
+            "var b = $jscomp$destructuring$var4[foo3()];",
+            "var c = $jscomp$destructuring$var0[foo4()]"));
+
+    test(
+        "var [a] = foo(); var [{b}] = foo(); var [{...c}] = foo();",
+        lines(
+            // var [a] = foo();
+            "var [a] = foo();",
+            // var [{b}] = foo();
+            "var [{b}] = foo();",
+            // var [{...c}] = foo();
+            "var $jscomp$destructuring$var0 = $jscomp.makeIterator(foo());",
+            "var $jscomp$destructuring$var1 = $jscomp$destructuring$var0.next().value;",
+            "var $jscomp$destructuring$var2 = Object.assign({},$jscomp$destructuring$var1);",
+            "var c = $jscomp$destructuring$var2"));
+  }
+
   @Override
   protected Compiler createCompiler() {
     return new NoninjectingCompiler();
diff --git a/test/com/google/javascript/jscomp/EsNextToEs8ConverterTest.java b/test/com/google/javascript/jscomp/EsNextToEs8ConverterTest.java
index 7bde3b33a39..65e7e749540 100644
--- a/test/com/google/javascript/jscomp/EsNextToEs8ConverterTest.java
+++ b/test/com/google/javascript/jscomp/EsNextToEs8ConverterTest.java
@@ -55,397 +55,4 @@ public void testObjectLiteralWithSpread() {
         "({first, [foo()]: baz(), ...spread});",
         "Object.assign({}, {first, [foo()]: baz()}, spread)");
   }
-
-  public void testObjectPatternWithRestDecl() {
-    test(
-        "var {a: b, c: d, ...rest} = foo();",
-        lines(
-            "var $jscomp$objpattern$var0 = foo(),",
-            "    {a: b, c: d} = $jscomp$objpattern$var0,",
-            "    $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "    rest = (delete $jscomp$objpattern$var1.a,",
-            "            delete $jscomp$objpattern$var1.c,",
-            "            $jscomp$objpattern$var1);"));
-
-    test(
-        "const {a: b, c: d, ...rest} = foo();",
-        lines(
-            "const $jscomp$objpattern$var0 = foo(),",
-            "      {a: b, c: d} = $jscomp$objpattern$var0,",
-            "      $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "      rest = (delete $jscomp$objpattern$var1.a,",
-            "              delete $jscomp$objpattern$var1.c,",
-            "              $jscomp$objpattern$var1);"));
-
-    test(
-        "let {a: b, c: d, ...rest} = foo();",
-        lines(
-            "let $jscomp$objpattern$var0 = foo(),",
-            "    {a: b, c: d} = $jscomp$objpattern$var0,",
-            "    $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "    rest = (delete $jscomp$objpattern$var1.a,",
-            "            delete $jscomp$objpattern$var1.c,",
-            "            $jscomp$objpattern$var1);"));
-
-    test(
-        "var pre = foo(), {a: b, c: d, ...rest} = foo();",
-        lines(
-            "var pre = foo(),",
-            "    $jscomp$objpattern$var0 = foo(),",
-            "    {a: b, c: d} = $jscomp$objpattern$var0,",
-            "    $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "    rest = (delete $jscomp$objpattern$var1.a,",
-            "            delete $jscomp$objpattern$var1.c,",
-            "            $jscomp$objpattern$var1);"));
-
-    test(
-        "var {a: b, c: d, ...rest} = foo(), post = foo();",
-        lines(
-            "var $jscomp$objpattern$var0 = foo(),",
-            "    {a: b, c: d} = $jscomp$objpattern$var0,",
-            "    $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "    rest = (delete $jscomp$objpattern$var1.a,",
-            "            delete $jscomp$objpattern$var1.c,",
-            "            $jscomp$objpattern$var1),",
-            "    post = foo();"));
-
-    test(
-        "var pre = foo(), {a: b, c: d, ...rest} = foo(), post = foo();",
-        lines(
-            "var pre = foo(),",
-            "    $jscomp$objpattern$var0 = foo(),",
-            "    {a: b, c: d} = $jscomp$objpattern$var0,",
-            "    $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "    rest = (delete $jscomp$objpattern$var1.a,",
-            "            delete $jscomp$objpattern$var1.c,",
-            "            $jscomp$objpattern$var1),",
-            "    post = foo();"));
-
-    test(
-        "var {a: b1, c: d1, ...rest1} = foo(), {a: b2, c: d2, ...rest2} = foo();",
-        lines(
-            "var $jscomp$objpattern$var0 = foo(),",
-            "    {a: b1, c: d1} = $jscomp$objpattern$var0,",
-            "    $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "    rest1 = (delete $jscomp$objpattern$var1.a,",
-            "             delete $jscomp$objpattern$var1.c,",
-            "             $jscomp$objpattern$var1),",
-            "    $jscomp$objpattern$var2 = foo(),",
-            "    {a: b2, c: d2} = $jscomp$objpattern$var2,",
-            "    $jscomp$objpattern$var3 = Object.assign({}, $jscomp$objpattern$var2),",
-            "    rest2 = (delete $jscomp$objpattern$var3.a,",
-            "             delete $jscomp$objpattern$var3.c,",
-            "             $jscomp$objpattern$var3);"));
-
-    test(
-        "var {...rest} = foo();",
-        lines(
-            "var $jscomp$objpattern$var0 = foo(),",
-            "    {} = $jscomp$objpattern$var0,",
-            "    $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "    rest = $jscomp$objpattern$var1;"));
-  }
-
-  public void testObjectPatternWithRestAssignStatement() {
-    test(
-        "var b,d,rest; ({a: b, c: d, ...rest} = foo());",
-        lines(
-            "var b,d,rest;",
-            "(()=>{",
-            "      let $jscomp$objpattern$var0 = foo();",
-            "      let $jscomp$objpattern$var1;",
-            "      ({a: b, c: d} = $jscomp$objpattern$var0),",
-            "        $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "        rest = (delete $jscomp$objpattern$var1.a,",
-            "                delete $jscomp$objpattern$var1.c,",
-            "                $jscomp$objpattern$var1);",
-            "})();"));
-
-    test(
-        "var b,d,rest,pre; pre = foo(), {a: b, c: d, ...rest} = foo();",
-        lines(
-            "var b,d,rest,pre;",
-            "pre = foo(),",
-            "(()=>{",
-            "      let $jscomp$objpattern$var0 = foo();",
-            "      let $jscomp$objpattern$var1;",
-            "      ({a: b, c: d} = $jscomp$objpattern$var0),",
-            "        $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "        rest = (delete $jscomp$objpattern$var1.a,",
-            "                delete $jscomp$objpattern$var1.c,",
-            "                $jscomp$objpattern$var1);",
-            "})();"));
-
-    test(
-        "var b,d,rest,post; ({a: b, c: d, ...rest} = foo()), post = foo();",
-        lines(
-            "var b,d,rest,post;",
-            "(()=>{",
-            "      let $jscomp$objpattern$var0 = foo();",
-            "      let $jscomp$objpattern$var1;",
-            "      ({a: b, c: d} = $jscomp$objpattern$var0),",
-            "        $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "        rest = (delete $jscomp$objpattern$var1.a,",
-            "                delete $jscomp$objpattern$var1.c,",
-            "                $jscomp$objpattern$var1);",
-            "})(),",
-            "post = foo();"));
-
-    test(
-        "var b,d,rest,pre,post; pre = foo(), {a: b, c: d, ...rest} = foo(), post = foo();",
-        lines(
-            "var b,d,rest,pre,post;",
-            "pre = foo(),",
-            "(()=>{",
-            "      let $jscomp$objpattern$var0 = foo();",
-            "      let $jscomp$objpattern$var1;",
-            "      ({a: b, c: d} = $jscomp$objpattern$var0),",
-            "        $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "        rest = (delete $jscomp$objpattern$var1.a,",
-            "                delete $jscomp$objpattern$var1.c,",
-            "                $jscomp$objpattern$var1);",
-            "})(),",
-            "post = foo();"));
-
-    test(
-        lines(
-            "var b1,d1,rest1,b2,d2,rest2;",
-            "({a: b1, c: d1, ...rest1} = foo(),",
-            " {a: b2, c: d2, ...rest2} = foo());"),
-        lines(
-            "var b1,d1,rest1,b2,d2,rest2;",
-            "(()=>{",
-            "      let $jscomp$objpattern$var0 = foo();",
-            "      let $jscomp$objpattern$var1;",
-            "      ({a: b1, c: d1} = $jscomp$objpattern$var0),",
-            "        $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "        rest1 = (delete $jscomp$objpattern$var1.a,",
-            "              delete $jscomp$objpattern$var1.c,",
-            "              $jscomp$objpattern$var1);",
-            "})(),",
-            "(()=>{",
-            "      let $jscomp$objpattern$var2 = foo();",
-            "      let $jscomp$objpattern$var3;",
-            "      ({a: b2, c: d2} = $jscomp$objpattern$var2),",
-            "        $jscomp$objpattern$var3 = Object.assign({}, $jscomp$objpattern$var2),",
-            "        rest2 = (delete $jscomp$objpattern$var3.a,",
-            "               delete $jscomp$objpattern$var3.c,",
-            "               $jscomp$objpattern$var3);",
-            "})();"));
-  }
-
-  public void testObjectPatternWithRestAssignExpr() {
-    test(
-        "var x,b,d,rest; x = ({a: b, c: d, ...rest} = foo());",
-        lines(
-            "var x,b,d,rest;",
-            "x = (()=>{",
-            "    let $jscomp$objpattern$var0 = foo();",
-            "    let $jscomp$objpattern$var1;",
-            "    ({a: b, c: d} = $jscomp$objpattern$var0),",
-            "      $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "      rest = (delete $jscomp$objpattern$var1.a,",
-            "              delete $jscomp$objpattern$var1.c,",
-            "              $jscomp$objpattern$var1);",
-            "      return $jscomp$objpattern$var0",
-            "})();"));
-
-    test(
-        "var x,b,d,rest; baz({a: b, c: d, ...rest} = foo());",
-        lines(
-            "var x,b,d,rest;",
-            "baz((()=>{",
-            "    let $jscomp$objpattern$var0 = foo();",
-            "    let $jscomp$objpattern$var1;",
-            "    ({a: b, c: d} = $jscomp$objpattern$var0),",
-            "      $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "      rest = (delete $jscomp$objpattern$var1.a,",
-            "              delete $jscomp$objpattern$var1.c,",
-            "              $jscomp$objpattern$var1);",
-            "      return $jscomp$objpattern$var0",
-            "})());"));
-  }
-
-  public void testObjectPatternWithRestForOf() {
-    test(
-        "for ({a: b, c: d, ...rest} of foo()) { console.log(rest.z); }",
-        lines(
-            "for (let $jscomp$objpattern$var0 of foo()) {",
-            "    let $jscomp$objpattern$var1;",
-            "    ({a: b, c: d} = $jscomp$objpattern$var0),",
-            "      $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "      rest = (delete $jscomp$objpattern$var1.a,",
-            "              delete $jscomp$objpattern$var1.c,",
-            "              $jscomp$objpattern$var1);",
-            "    console.log(rest.z);",
-            "}"));
-
-    test(
-        "for (var {a: b, c: d, ...rest} of foo()) { console.log(rest.z); }",
-        lines(
-            "for (let $jscomp$objpattern$var0 of foo()) {",
-            "    var {a: b, c: d} = $jscomp$objpattern$var0,",
-            "      $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "      rest = (delete $jscomp$objpattern$var1.a,",
-            "              delete $jscomp$objpattern$var1.c,",
-            "              $jscomp$objpattern$var1);",
-            "    console.log(rest.z);",
-            "}"));
-
-    test(
-        "for (let {a: b, c: d, ...rest} of foo()) { console.log(rest.z); }",
-        lines(
-            "for (let $jscomp$objpattern$var0 of foo()) {",
-            "    let {a: b, c: d} = $jscomp$objpattern$var0,",
-            "      $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "      rest = (delete $jscomp$objpattern$var1.a,",
-            "              delete $jscomp$objpattern$var1.c,",
-            "              $jscomp$objpattern$var1);",
-            "    console.log(rest.z);",
-            "}"));
-
-    test(
-        "for (const {a: b, c: d, ...rest} of foo()) { console.log(rest.z); }",
-        lines(
-            "for (let $jscomp$objpattern$var0 of foo()) {",
-            "    const {a: b, c: d} = $jscomp$objpattern$var0,",
-            "      $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "      rest = (delete $jscomp$objpattern$var1.a,",
-            "              delete $jscomp$objpattern$var1.c,",
-            "              $jscomp$objpattern$var1);",
-            "    console.log(rest.z);",
-            "}"));
-
-    test(
-        "for (var {a: b, [baz()]: d, ...rest} of foo()) { console.log(rest.z); }",
-        lines(
-            "for (let $jscomp$objpattern$var0 of foo()) {",
-            "    let $jscomp$objpattern$var2 = baz();",
-            "    var {a: b, [$jscomp$objpattern$var2]: d} = $jscomp$objpattern$var0,",
-            "      $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "      rest = (delete $jscomp$objpattern$var1.a,",
-            "              delete $jscomp$objpattern$var1[$jscomp$objpattern$var2],",
-            "              $jscomp$objpattern$var1);",
-            "    console.log(rest.z);",
-            "}"));
-  }
-
-  public void testObjectPatternWithRestAndComputedPropertyName() {
-    test(
-        "var {a: b = 3, [bar()]: d, [baz()]: e, ...rest} = foo();",
-        lines(
-            "var $jscomp$objpattern$var0 = foo(),",
-            "    $jscomp$objpattern$var2 = bar(),",
-            "    $jscomp$objpattern$var3 = baz(),",
-            "    {a: b = 3, ",
-            "     [$jscomp$objpattern$var2]: d,",
-            "     [$jscomp$objpattern$var3]: e} = $jscomp$objpattern$var0,",
-            "     $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "     rest = (delete $jscomp$objpattern$var1.a,",
-            "            delete $jscomp$objpattern$var1[$jscomp$objpattern$var2],",
-            "            delete $jscomp$objpattern$var1[$jscomp$objpattern$var3],",
-            "            $jscomp$objpattern$var1);"));
-  }
-
-  public void testObjectPatternWithRestAndDefaults() {
-    test(
-        "var {a = 3, ...rest} = foo();",
-        lines(
-            "var $jscomp$objpattern$var0 = foo(),",
-            "    {a = 3} = $jscomp$objpattern$var0,",
-            "    $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "    rest = (delete $jscomp$objpattern$var1.a,",
-            "            $jscomp$objpattern$var1);"));
-
-    test(
-        "var {[bar()]:a = 3, 'b c':b = 12, ...rest} = foo();",
-        lines(
-            "var $jscomp$objpattern$var0 = foo(),",
-            "    $jscomp$objpattern$var2 = bar(),",
-            "    {[$jscomp$objpattern$var2]:a = 3, 'b c':b = 12} = $jscomp$objpattern$var0,",
-            "    $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "    rest = (delete $jscomp$objpattern$var1[$jscomp$objpattern$var2],",
-            "            delete $jscomp$objpattern$var1['b c'],",
-            "            $jscomp$objpattern$var1);"));
-  }
-
-  public void testObjectPatternWithRestInCatch() {
-    test(
-        "try {} catch ({first, second, ...rest}) { console.log(rest.z); }",
-        lines(
-            "try {}",
-            "catch ($jscomp$objpattern$var0) {",
-            "  let {first, second} = $jscomp$objpattern$var0,",
-            "      $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "      rest = (delete $jscomp$objpattern$var1.first, ",
-            "              delete $jscomp$objpattern$var1.second, ",
-            "              $jscomp$objpattern$var1);",
-            "  console.log(rest.z);",
-            "}"));
-  }
-
-  public void testObjectPatternWithRestAssignReturn() {
-    test(
-        "function f() { return {x:a, ...rest} = foo(); }",
-        lines(
-            "function f() {",
-            "  return (()=>{",
-            "    let $jscomp$objpattern$var0=foo();",
-            "    let $jscomp$objpattern$var1;",
-            "    ({x:a}=$jscomp$objpattern$var0),",
-            "      $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "      rest=(delete $jscomp$objpattern$var1.x,",
-            "            $jscomp$objpattern$var1);",
-            "    return $jscomp$objpattern$var0",
-            "  })();",
-            "}"));
-  }
-
-  public void testObjectPatternWithRestParamList() {
-    test(
-        "function f({x = a(), ...rest}, y=b()) { console.log(y); }",
-        lines(
-            "function f($jscomp$objpattern$var0, y=b()) {",
-            "  let {x=a()} = $jscomp$objpattern$var0,",
-            "      $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "      rest=(delete $jscomp$objpattern$var1.x,",
-            "            $jscomp$objpattern$var1);",
-            "  console.log(y);",
-            "}"));
-
-    test(
-        "function f({x = a(), ...rest}={}, y=b()) { console.log(y); }",
-        lines(
-            "function f($jscomp$objpattern$var0={}, y=b()) {",
-            "  let {x=a()} = $jscomp$objpattern$var0,",
-            "      $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "      rest = (delete $jscomp$objpattern$var1.x,",
-            "              $jscomp$objpattern$var1);",
-            "  console.log(y);",
-            "}"));
-  }
-
-  public void testObjectPatternWithRestArrowParamList() {
-    test(
-        "var f = ({x = a(), ...rest}, y=b()) => { console.log(y); };",
-        lines(
-            "var f = ($jscomp$objpattern$var0, y=b()) => {",
-            "  let {x=a()} = $jscomp$objpattern$var0,",
-            "      $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "      rest = (delete $jscomp$objpattern$var1.x,",
-            "              $jscomp$objpattern$var1);",
-            "  console.log(y);",
-            "};"));
-
-    test(
-        "var f = ({x = a(), ...rest}={}, y=b()) => { console.log(y); };",
-        lines(
-            "var f = ($jscomp$objpattern$var0={}, y=b()) => {",
-            "  let {x=a()} = $jscomp$objpattern$var0,",
-            "  $jscomp$objpattern$var1 = Object.assign({}, $jscomp$objpattern$var0),",
-            "  rest = (delete $jscomp$objpattern$var1.x,",
-            "          $jscomp$objpattern$var1);",
-            "  console.log(y);",
-            "};"));
-  }
 }
diff --git a/test/com/google/javascript/jscomp/IntegrationTest.java b/test/com/google/javascript/jscomp/IntegrationTest.java
index af1822a815e..dcf9a9daaa2 100644
--- a/test/com/google/javascript/jscomp/IntegrationTest.java
+++ b/test/com/google/javascript/jscomp/IntegrationTest.java
@@ -5255,30 +5255,32 @@ public void testDestructuringRest() {
     options.setLanguageOut(LanguageMode.ECMASCRIPT_2017);
 
     test(options, "const {y} = {}", "const {y} = {}");
+
     test(
         options,
         "const {...y} = {}",
         lines(
-            "const $jscomp$objpattern$var0={};",
-            "const {}=$jscomp$objpattern$var0;",
-            "const $jscomp$objpattern$var1=Object.assign({}, $jscomp$objpattern$var0);",
-            "const y=$jscomp$objpattern$var1"));
+            "var $jscomp$destructuring$var0 = {};",
+            "var $jscomp$destructuring$var1 = Object.assign({},$jscomp$destructuring$var0);",
+            "const y = $jscomp$destructuring$var1"));
 
     test(
         options,
         "function foo({ a, b, ...c}) { try { foo() } catch({...m}) {} }",
         lines(
-            "function foo($jscomp$objpattern$var0) {",
-            "  let {a,b}=$jscomp$objpattern$var0;",
-            "  let $jscomp$objpattern$var1=Object.assign({}, $jscomp$objpattern$var0);",
-            "  let c=(delete $jscomp$objpattern$var1.a,",
-            "         delete $jscomp$objpattern$var1.b,",
-            "         $jscomp$objpattern$var1)",
-            "  try { foo() }",
-            "  catch ($jscomp$objpattern$var2) {",
-            "    let {}=$jscomp$objpattern$var2;",
-            "    let $jscomp$objpattern$var3=Object.assign({}, $jscomp$objpattern$var2)",
-            "    let m=$jscomp$objpattern$var3",
+            "function foo($jscomp$destructuring$var0){",
+            "  var $jscomp$destructuring$var1 = $jscomp$destructuring$var0;",
+            "  var $jscomp$destructuring$var2 = Object.assign({},$jscomp$destructuring$var1);",
+            "  var a=$jscomp$destructuring$var1.a;",
+            "  var b=$jscomp$destructuring$var1.b;",
+            "  var c= (delete $jscomp$destructuring$var2.a,",
+            "          delete $jscomp$destructuring$var2.b,",
+            "          $jscomp$destructuring$var2);",
+            "  try{ foo() }",
+            "  catch ($jscomp$destructuring$var3) {",
+            "    var $jscomp$destructuring$var4 = $jscomp$destructuring$var3;",
+            "    var $jscomp$destructuring$var5 = Object.assign({}, $jscomp$destructuring$var4);",
+            "    let m = $jscomp$destructuring$var5",
             "  }",
             "}"));
   }
diff --git a/test/com/google/javascript/jscomp/MultiPassTest.java b/test/com/google/javascript/jscomp/MultiPassTest.java
index c15a4afff09..c619d1eb024 100644
--- a/test/com/google/javascript/jscomp/MultiPassTest.java
+++ b/test/com/google/javascript/jscomp/MultiPassTest.java
@@ -19,6 +19,7 @@
 import static com.google.javascript.jscomp.parsing.parser.FeatureSet.ES8_MODULES;
 
 import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
+import com.google.javascript.jscomp.Es6RewriteDestructuring.ObjectDestructuringRewriteMode;
 import com.google.javascript.jscomp.parsing.parser.FeatureSet;
 import java.util.ArrayList;
 import java.util.List;
@@ -433,7 +434,10 @@ private void addDestructuringPass() {
         new PassFactory("destructuringPass", true) {
           @Override
           protected CompilerPass create(final AbstractCompiler compiler) {
-            return new Es6RewriteDestructuring(compiler);
+            return new Es6RewriteDestructuring.Builder(compiler)
+                .setDestructuringRewriteMode(
+                    ObjectDestructuringRewriteMode.REWRITE_ALL_OBJECT_PATTERNS)
+                .build();
           }
 
           @Override