All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.Es6RewriteDestructuring Maven / Gradle / Ivy

/*
 * Copyright 2015 The Closure Compiler Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.javascript.jscomp;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.javascript.jscomp.AstFactory.type;
import static com.google.javascript.jscomp.DiagnosticType.error;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.javascript.jscomp.colors.StandardColors;
import com.google.javascript.jscomp.parsing.ParsingUtil;
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.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.StaticScope;
import com.google.javascript.rhino.Token;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.Iterator;

/**
 * Rewrites destructuring patterns and default parameters to valid ES3 code or to a different form
 * of destructuring.
 */
public final class Es6RewriteDestructuring implements NodeTraversal.Callback, CompilerPass {

  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 final ObjectDestructuringRewriteMode rewriteMode; private final AstFactory astFactory; private final StaticScope namespace; 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; private Es6RewriteDestructuring(Builder builder) { this.compiler = builder.compiler; this.rewriteMode = builder.rewriteMode; this.astFactory = compiler.createAstFactory(); this.namespace = compiler.getTranspilationNamespace(); switch (this.rewriteMode) { case REWRITE_ALL_OBJECT_PATTERNS: this.featuresToTriggerRunningPass = FeatureSet.BARE_MINIMUM.with( Feature.DEFAULT_PARAMETERS, Feature.ARRAY_DESTRUCTURING, Feature.ARRAY_PATTERN_REST, Feature.OBJECT_DESTRUCTURING); // 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.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; } @CanIgnoreReturnValue 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) { checkState(patternNestingStack.isEmpty()); NodeTraversal.traverse(compiler, root, this); TranspilationPasses.maybeMarkFeaturesAsTranspiledAway(compiler, root, featuresToMarkAsRemoved); checkState(patternNestingStack.isEmpty()); } @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { if (n.isScript()) { FeatureSet scriptFeatures = NodeUtil.getFeatureSetOfScript(n); return scriptFeatures.containsAtLeastOneOf(featuresToTriggerRunningPass); } switch (n.getToken()) { case FUNCTION: ensureArrowFunctionsHaveBlockBodies(t, n); break; case PARAM_LIST: 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; } return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getToken()) { case ARRAY_PATTERN: case OBJECT_PATTERN: visitPattern(t, n); if (n == this.patternNestingStack.getLast().pattern) { this.patternNestingStack.removeLast(); } break; default: break; } } /** * If the function is an arrow function, wrap the body in a block if it is not already a block. */ // TODO(bradfordcsmith): This should be separated from this pass. private void ensureArrowFunctionsHaveBlockBodies(NodeTraversal t, Node function) { Node body = function.getLastChild(); if (!body.isBlock()) { body.detach(); Node replacement = IR.block(IR.returnNode(body)).srcrefTreeIfMissing(body); function.addChildToBack(replacement); t.reportCodeChange(); } } /** Pulls all default and destructuring parameters out 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(); Node next = null; for (Node param = paramList.getFirstChild(); param != null; param = next) { next = param.getNext(); if (param.isDefaultValue()) { Node nameOrPattern = param.removeFirstChild(); // We'll be cloning nameOrPattern below, and we don't want to clone the JSDoc info with it JSDocInfo jsDoc = nameOrPattern.getJSDocInfo(); nameOrPattern.setJSDocInfo(null); Node defaultValue = param.removeFirstChild(); Node newParam; // Treat name=undefined (and equivalent) as if it was just name. There // is no need to generate a (name===void 0?void 0:name) statement for // such arguments. boolean isNoop = false; if (!nameOrPattern.isName()) { // Do not try to optimize unless nameOrPattern is a simple name. } else if (defaultValue.isName()) { isNoop = "undefined".equals(defaultValue.getString()); } else if (defaultValue.isVoid()) { // Any kind of 'void literal' is fine, but 'void fun()' or anything // else with side effects isn't. We're not trying to be particularly // smart here and treat 'void {}' for example as if it could cause side effects. isNoop = NodeUtil.isImmutableValue(defaultValue.getFirstChild()); } if (isNoop) { newParam = nameOrPattern.cloneTree(); } else { newParam = nameOrPattern.isName() ? nameOrPattern : createTempVarNameNode(getTempVariableName(), type(nameOrPattern)); Node lhs = nameOrPattern.cloneTree(); Node rhs = defaultValueHook(newParam.cloneTree(), defaultValue); Node newStatement = nameOrPattern.isName() ? IR.exprResult(astFactory.createAssign(lhs, rhs)) : IR.var(lhs, rhs); newStatement.srcrefTreeIfMissing(param); if (insertSpot == null) { body.addChildToFront(newStatement); } else { newStatement.insertAfter(insertSpot); } insertSpot = newStatement; } param.replaceWith(newParam); newParam.setJSDocInfo(jsDoc); compiler.reportChangeToChangeScope(function); } else if (param.isDestructuringPattern()) { insertSpot = replacePatternParamWithTempVar(function, insertSpot, param, getTempVariableName()); compiler.reportChangeToChangeScope(function); } else if (param.isRest() && param.getFirstChild().isDestructuringPattern()) { insertSpot = replacePatternParamWithTempVar( function, insertSpot, param.getFirstChild(), getTempVariableName()); compiler.reportChangeToChangeScope(function); } } } /** * Replace a destructuring pattern parameter with a a temporary parameter name and add a new local * variable declaration to the function assigning the temporary parameter to the pattern. * *

Note: Rewrites of variable declaration destructuring will happen later to rewrite this * declaration as non-destructured code. * * @param insertSpot The local variable declaration will be inserted after this statement. * @param tempVarName the name to use for the temporary variable * @return the declaration statement that was generated for the local variable */ private Node replacePatternParamWithTempVar( Node function, Node insertSpot, Node patternParam, String tempVarName) { // Convert `function f([a, b]) {}` to `function f(tempVar) { var [a, b] = tempVar; }` AstFactory.Type paramType = type(patternParam); Node newParam = createTempVarNameNode(tempVarName, paramType); newParam.setJSDocInfo(patternParam.getJSDocInfo()); patternParam.replaceWith(newParam); Node newDecl = IR.var(patternParam, createTempVarNameNode(tempVarName, paramType)); newDecl.srcrefTreeIfMissing(patternParam); if (insertSpot == null) { function.getLastChild().addChildToFront(newDecl); } else { newDecl.insertAfter(insertSpot); } return newDecl; } /** * Creates a new name node and adds the constant name property because a constant variable is used * (the compiler only assigns $jscomp$destructuring$var[num] once) */ private Node createTempVarNameNode(String name, AstFactory.Type type) { return astFactory.createConstantName(name, type); } /** Creates a new unique name to use for a pattern we need to rewrite. */ private String getTempVariableName() { return DESTRUCTURING_TEMP_VAR + destructuringVarCounter++; } private void visitPattern(NodeTraversal t, Node pattern) { Node parent = pattern.getParent(); switch (parent.getToken()) { case DESTRUCTURING_LHS: { Node declaration = parent.getParent(); Node declarationParent = declaration.getParent(); if (declarationParent.isVanillaFor()) { visitDestructuringPatternInVanillaForInnerVars(t, pattern); } else if (NodeUtil.isEnhancedFor(declarationParent)) { visitDestructuringPatternInEnhancedForInnerVars(pattern); } else { replacePattern(t, pattern, pattern.getNext(), declaration, declaration); } } break; case ASSIGN: if (parent.getParent().isExprResult()) { replacePattern(t, pattern, pattern.getNext(), parent, parent.getParent()); } else { wrapAssignOrDestructuringInCallToArrow(t, parent); } break; case OBJECT_REST: case ITER_REST: case STRING_KEY: case ARRAY_PATTERN: case DEFAULT_VALUE: case COMPUTED_PROP: // Nested pattern; do nothing. We will visit it after rewriting the parent. break; case FOR_OF: case FOR_IN: case FOR_AWAIT_OF: visitDestructuringPatternInEnhancedForWithOuterVars(pattern); break; case CATCH: visitDestructuringPatternInCatch(t, pattern); break; default: throw new IllegalStateException("unexpected parent"); } } /** * Transpiles a destructuring pattern in a declaration or assignment to ES5 * * @param nodeToDetach a statement node containing the pattern. This method will replace the node * with one or more other statements. */ private void replacePattern( NodeTraversal t, Node pattern, Node rhs, Node parent, Node nodeToDetach) { checkArgument(NodeUtil.isStatement(nodeToDetach), nodeToDetach); switch (pattern.getToken()) { case ARRAY_PATTERN: replaceArrayPattern(t, pattern, rhs, parent, nodeToDetach); break; case OBJECT_PATTERN: replaceObjectPattern(t, pattern, rhs, parent, nodeToDetach); break; default: throw new IllegalStateException("unexpected"); } } /** * Convert 'var {a: b, c: d} = rhs' to: * * @const var temp = rhs; var b = temp.a; var d = temp.c; */ private void replaceObjectPattern( NodeTraversal t, Node objectPattern, Node rhs, Node parent, Node nodeToDetach) { String tempVarName = getTempVariableName(); final AstFactory.Type tempVarType = type(objectPattern); 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 = getTempVariableName(); } 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; } } // create the declaration `var temp = rhs;` Node tempDecl = IR.var(createTempVarNameNode(tempVarName, tempVarType), rhs.detach()) .srcrefTreeIfMissing(objectPattern); // TODO(tbreisacher): Remove the "if" and add this JSDoc unconditionally. if (parent.isConst()) { JSDocInfo.Builder jsDoc = JSDocInfo.builder(); jsDoc.recordConstancy(); tempDecl.setJSDocInfo(jsDoc.build()); } tempDecl.insertBefore(nodeToDetach); for (Node child = objectPattern.getFirstChild(), next; child != null; child = next) { next = child.getNext(); final Node newLHS; final Node newRHS; if (child.isStringKey()) { // const {a: b} = obj; Node tempVarNameNode = createTempVarNameNode(tempVarName, tempVarType); Node getprop = child.isQuotedStringKey() ? astFactory.createGetElem( tempVarNameNode, astFactory.createString(child.getString())) : astFactory.createGetProp(tempVarNameNode, child.getString(), tempVarType); Node value = child.removeFirstChild(); if (!value.isDefaultValue()) { newLHS = value; newRHS = getprop; } else { newLHS = value.removeFirstChild(); Node defaultValue = value.removeFirstChild(); newRHS = defaultValueHook(getprop, defaultValue); } if (propsToDeleteForRest != null) { propsToDeleteForRest.add(child); } } else if (child.isComputedProp()) { // const {[propExpr]: newLHS = defaultValue} = newRHS; boolean hasDefault = child.getLastChild().isDefaultValue(); final Node defaultValue; Node propExpr = child.removeFirstChild(); if (hasDefault) { Node defaultNode = child.getLastChild(); newLHS = defaultNode.removeFirstChild(); defaultValue = defaultNode.removeFirstChild(); } else { newLHS = child.removeFirstChild(); defaultValue = null; } if (propsToDeleteForRest != null) { // A "...rest" variable is present and result of computation must be cached String exprEvalTempVarName = getTempVariableName(); Node exprEvalTempVarModel = createTempVarNameNode(exprEvalTempVarName, type(propExpr)); // clone this node Node exprEvalDecl = IR.var(exprEvalTempVarModel.cloneNode(), propExpr); exprEvalDecl.srcrefTreeIfMissing(child); exprEvalDecl.insertBefore(nodeToDetach); propExpr = exprEvalTempVarModel.cloneNode(); propsToDeleteForRest.add(exprEvalTempVarModel.cloneNode()); } if (hasDefault) { // tempVarName[propExpr] Node getelem = astFactory.createGetElem(createTempVarNameNode(tempVarName, tempVarType), propExpr); // var tempVarName2 = tempVarName1[propExpr] String intermediateTempVarName = getTempVariableName(); Node intermediateDecl = IR.var(createTempVarNameNode(intermediateTempVarName, type(getelem)), getelem); intermediateDecl.srcrefTreeIfMissing(child); intermediateDecl.insertBefore(nodeToDetach); // tempVarName2 === undefined ? defaultValue : tempVarName2 newRHS = defaultValueHook( createTempVarNameNode(intermediateTempVarName, type(getelem)), defaultValue); } else { newRHS = astFactory.createGetElem(createTempVarNameNode(tempVarName, type(newLHS)), propExpr); } } else if (child.isRest()) { if (next != null) { throw new IllegalStateException("object rest may not be followed by any properties"); } // TODO(b/116532470): see if casting this to a more specific type fixes disambiguation Node assignCall = astFactory.createCall( astFactory.createQName(this.namespace, "Object.assign"), type(StandardColors.TOP_OBJECT)); assignCall.addChildToBack(astFactory.createObjectLit()); assignCall.addChildToBack(createTempVarNameNode(tempVarName, tempVarType)); Node restTempDecl = IR.var(createTempVarNameNode(restTempVarName, tempVarType), assignCall); restTempDecl.srcrefTreeIfMissing(objectPattern); restTempDecl.insertAfter(tempDecl); Node restName = child.getOnlyChild(); // e.g. get `rest` from `const {...rest} = {};` if (restName.getString().startsWith(DESTRUCTURING_TEMP_VAR)) { newLHS = createTempVarNameNode(restName.getString(), type(restName)); } else { newLHS = astFactory.createName(restName.getString(), type(restName)); } newRHS = objectPatternRestRHS(objectPattern, child, restTempVarName, propsToDeleteForRest); } else { throw new IllegalStateException("unexpected child"); } Node newNode; if (NodeUtil.isNameDeclaration(parent)) { if (parent.isConst()) { newLHS.putBooleanProp(Node.IS_CONSTANT_NAME, true); } newNode = IR.declaration(newLHS, newRHS, parent.getToken()); } else if (parent.isAssign()) { newNode = IR.exprResult(astFactory.createAssign(newLHS, newRHS)); } else { throw new IllegalStateException("not reached"); } newNode.srcrefTreeIfMissing(child); newNode.insertBefore(nodeToDetach); // Explicitly visit the LHS of the new node since it may be a nested // destructuring pattern. visit(t, newLHS, newLHS.getParent()); } nodeToDetach.detach(); 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 restTempVarModel = createTempVarNameNode(restTempVarName, type(objectPattern)); Node result = restTempVarModel.cloneNode(); if (!statedProperties.isEmpty()) { Iterator propItr = statedProperties.iterator(); Node comma = deletionNodeForRestProperty(restTempVarModel.cloneNode(), propItr.next()); while (propItr.hasNext()) { comma = astFactory.createComma( comma, deletionNodeForRestProperty(restTempVarModel.cloneNode(), propItr.next())); } result = astFactory.createComma(comma, result); } result.srcrefTreeIfMissing(rest); return result; } private Node deletionNodeForRestProperty(Node restTempVarNameNode, Node property) { final Node get; switch (property.getToken()) { case STRING_KEY: get = property.isQuotedStringKey() ? astFactory.createGetElem( restTempVarNameNode, astFactory.createString(property.getString())) : astFactory.createGetPropWithUnknownType( restTempVarNameNode, property.getString()); break; case NAME: get = astFactory.createGetElem(restTempVarNameNode, property); break; default: throw new IllegalStateException( "Unexpected property to delete node: " + property.toStringTree()); } return astFactory.createDelProp(get); } /** * Convert
var [x, y] = rhs
 to:
   * 
   *   var temp = $jscomp.makeIterator(rhs);
   *   var x = temp.next().value;
   *   var y = temp.next().value;
   * 
*/ 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 = getTempVariableName(); Node makeIteratorCall = astFactory.createJSCompMakeIteratorCall(rhs.detach(), this.namespace); Node tempDecl = astFactory.createSingleVarNameDeclaration(tempVarName, makeIteratorCall); Node tempVarModel = tempDecl.getFirstChild(); tempDecl.srcrefTreeIfMissing(arrayPattern); tempDecl.insertBefore(nodeToDetach); for (Node child = arrayPattern.getFirstChild(), next; child != null; child = next) { next = child.getNext(); if (child.isEmpty()) { // Just call the next() method to advance the iterator, but throw away the value. Node nextCall = IR.exprResult( astFactory.createCallWithUnknownType( astFactory.createGetProp( tempVarModel.cloneNode(), "next", type(StandardColors.TOP_OBJECT)))); nextCall.srcrefTreeIfMissing(child); nextCall.insertBefore(nodeToDetach); continue; } Node newLHS; Node newRHS; if (child.isDefaultValue()) { // [x = defaultValue] = rhs; // becomes // var temp0 = $jscomp.makeIterator(rhs); // var temp1 = temp.next().value // x = (temp1 === undefined) ? defaultValue : temp1; String nextVarName = getTempVariableName(); // `temp.next().value` Node nextCallDotValue = astFactory.createGetPropWithUnknownType( astFactory.createCallWithUnknownType( astFactory.createGetPropWithUnknownType(tempVarModel.cloneNode(), "next")), "value"); AstFactory.Type nextVarType = type(nextCallDotValue); // `var temp1 = temp.next().value` Node var = IR.var(createTempVarNameNode(nextVarName, nextVarType), nextCallDotValue); var.srcrefTreeIfMissing(child); var.insertBefore(nodeToDetach); // `x` newLHS = child.removeFirstChild(); // `(temp1 === undefined) ? defaultValue : temp1; newRHS = defaultValueHook( createTempVarNameNode(nextVarName, nextVarType), child.getLastChild().detach()); } else if (child.isRest()) { // [...x] = rhs; // becomes // var temp = $jscomp.makeIterator(rhs); // x = $jscomp.arrayFromIterator(temp); newLHS = child.removeFirstChild(); newRHS = astFactory.createJscompArrayFromIteratorCall(tempVarModel.cloneNode(), namespace); } else { // LHS is just a name (or a nested pattern). // var [x] = rhs; // becomes // var temp = $jscomp.makeIterator(rhs); // var x = temp.next().value; newLHS = child.detach(); newRHS = astFactory.createGetProp( astFactory.createCallWithUnknownType( astFactory.createGetPropWithUnknownType(tempVarModel.cloneNode(), "next")), "value", type(child)); } Node newNode; if (parent.isAssign()) { Node assignment = astFactory.createAssign(newLHS, newRHS); newNode = IR.exprResult(assignment); } else { newNode = IR.declaration(newLHS, newRHS, parent.getToken()); } newNode.srcrefTreeIfMissing(arrayPattern); newNode.insertBefore(nodeToDetach); // Explicitly visit the LHS of the new node since it may be a nested // destructuring pattern. visit(t, newLHS, newLHS.getParent()); } nodeToDetach.detach(); t.reportCodeChange(); } /** * Replace ASSIGN or DESTRUCTURING_LHS with a IIFE that contains the transpiled destructuring. * *
   * Transform
   *   [x, y] = rhs
   * into
   *   {@code (() => {
   *     let temp0 = rhs;
   *     var temp1 = $jscomp.makeIterator(temp0);
   *     var x = temp0.next().value;
   *     var y = temp0.next().value;
   *     return temp0;
   *   })()}
   *
   * Transform
   *   {x: a, y: b} = rhs
   * into
   *   {@code (() => {
   *     let temp0 = rhs;
   *     var temp1 = temp0;
   *     var a = temp0.x;
   *     var b = temp0.y;
   *     return temp0;
   *   })()}
   * 
*/ private void wrapAssignOrDestructuringInCallToArrow(NodeTraversal t, Node assignment) { String tempVarName = getTempVariableName(); Node rhs = assignment.getLastChild().detach(); Node tempVarModel = createTempVarNameNode(tempVarName, type(rhs)); // let temp0 = rhs; Node newAssignment = IR.let(tempVarModel.cloneNode(), rhs); NodeUtil.addFeatureToScript(t.getCurrentScript(), Feature.LET_DECLARATIONS, compiler); // [x, y] = temp0; Node replacementExpr = astFactory.createAssign(assignment.removeFirstChild(), tempVarModel.cloneNode()); Node exprResult = IR.exprResult(replacementExpr); // return temp0; Node returnNode = IR.returnNode(tempVarModel.cloneNode()); // Create a function to hold these assignments: Node block = IR.block(newAssignment, exprResult, returnNode); Node arrowFn = astFactory.createZeroArgFunction(/* name= */ "", block, /* returnType= */ null); arrowFn.setIsArrowFunction(true); // Create a call to the function, and replace the pattern with the call. Node call = astFactory.createCall(arrowFn, type(rhs)); NodeUtil.addFeatureToScript(t.getCurrentScript(), Feature.ARROW_FUNCTIONS, compiler); call.srcrefTreeIfMissing(assignment); call.putBooleanProp(Node.FREE_CALL, true); assignment.replaceWith(call); NodeUtil.markNewScopesChanged(call, compiler); replacePattern( t, replacementExpr.getFirstChild(), replacementExpr.getLastChild(), replacementExpr, exprResult); } /** for (let [a, b, c] = arr; a < b; a++) */ private void visitDestructuringPatternInVanillaForInnerVars(NodeTraversal t, Node pattern) { checkArgument(pattern.isDestructuringPattern()); Node destructuringLhs = pattern.getParent(); Node declaration = destructuringLhs.getParent(); Node insertionPoint = declaration.getParent(); while (insertionPoint.getParent().isLabel()) { insertionPoint = insertionPoint.getParent(); } switch (declaration.getToken()) { case CONST: { Node block = IR.block().srcref(insertionPoint); insertionPoint.replaceWith(block); block.addChildToBack(insertionPoint); } // Fall through case VAR: { // Move any earlier variables out of the loop initializer for (Node c = declaration.getFirstChild(); c != destructuringLhs; c = declaration.getFirstChild()) { Node newDeclaration = declaration.cloneNode(); newDeclaration.addChildToBack(c.detach()); newDeclaration.insertBefore(insertionPoint); } // Move the pattern out of the initializer and transpile it Node newDeclaration = declaration.cloneNode(); newDeclaration.addChildToBack(destructuringLhs.detach()); newDeclaration.insertBefore(insertionPoint); this.replacePattern(t, pattern, pattern.getNext(), newDeclaration, newDeclaration); if (!declaration.hasChildren()) { declaration.replaceWith(IR.empty()); } } break; case LET: // See https://tc39.es/ecma262/#sec-createperiterationenvironment // for (let a, b, c, unusedTmp = (() => [a, b, c] = arr); a < b; a++) { ParsingUtil.getParamOrPatternNames( pattern, (name) -> name.cloneNode().insertBefore(destructuringLhs)); Node unusedVar = this.astFactory .createNameWithUnknownType(getTempVariableName() + "$unused") .srcref(destructuringLhs); destructuringLhs.replaceWith(unusedVar); unusedVar.addChildToBack(destructuringLhs); this.wrapAssignOrDestructuringInCallToArrow(t, destructuringLhs); } break; default: throw new IllegalStateException(declaration.toString()); } this.compiler.reportChangeToEnclosingScope(insertionPoint); NodeUtil.markNewScopesChanged(insertionPoint, compiler); } /** for (const [a, b, c] of arr) */ private void visitDestructuringPatternInEnhancedForInnerVars(Node pattern) { checkArgument(pattern.isDestructuringPattern()); String tempVarName = getTempVariableName(); Node destructuringLhs = pattern.getParent(); checkState(destructuringLhs.isDestructuringLhs()); Node declarationNode = destructuringLhs.getParent(); Node forNode = declarationNode.getParent(); checkState(NodeUtil.isEnhancedFor(forNode)); Node block = forNode.getLastChild(); destructuringLhs.replaceWith(createTempVarNameNode(tempVarName, type(pattern)).srcref(pattern)); Token declarationType = declarationNode.getToken(); Node decl = IR.declaration( pattern.detach(), createTempVarNameNode(tempVarName, type(pattern)), declarationType); decl.srcrefTreeIfMissing(pattern); // Move the body into an inner block to handle cases where declared variables in the for // loop initializer are shadowed by variables in the for loop body. e.g. // for (const [value] of []) { const value = 1; } Node newBlock = IR.block(decl); block.replaceWith(newBlock); newBlock.addChildToBack(block); } /** * for ([a, b, c] of arr) * *
   * Transform
   *   for ({x} of y) {}
   * into
   *   {@code var TEMP_VAR0;
   *   for (TEMP_VAR0 of y) {
   *     var TEMP_VAR1 = TEMP_VAR0;
   *     x = TEMP_VAR1.x;
   *   }}
   * 
*/ private void visitDestructuringPatternInEnhancedForWithOuterVars(Node pattern) { checkArgument(pattern.isDestructuringPattern()); String tempVarName = getTempVariableName(); Node forNode = pattern.getParent(); Node block = forNode.getLastChild(); Node name = createTempVarNameNode(tempVarName, type(pattern)); Node decl = IR.var(name); decl.srcrefTreeIfMissing(pattern); decl.insertBefore(forNode); Node clonedName = name.cloneNode(); clonedName.srcrefTreeIfMissing(pattern); pattern.replaceWith(clonedName); Node exprResult = IR.exprResult( astFactory.createAssign(pattern, createTempVarNameNode(tempVarName, type(pattern)))); exprResult.srcrefTreeIfMissing(pattern); block.addChildToFront(exprResult); } private void visitDestructuringPatternInCatch(NodeTraversal t, Node pattern) { String tempVarName = getTempVariableName(); Node catchBlock = pattern.getNext(); AstFactory.Type patternType = type(pattern); pattern.replaceWith(createTempVarNameNode(tempVarName, patternType)); catchBlock.addChildToFront( IR.declaration(pattern, createTempVarNameNode(tempVarName, patternType), Token.LET)); NodeUtil.addFeatureToScript(t.getCurrentScript(), Feature.LET_DECLARATIONS, compiler); } /** Helper for transpiling DEFAULT_VALUE trees. */ private Node defaultValueHook(Node getprop, Node defaultValue) { Node undefined = astFactory.createUndefinedValue(); undefined.makeNonIndexable(); Node getpropClone = getprop.cloneTree().setColor(getprop.getColor()); return astFactory.createHook( astFactory.createSheq(getprop, undefined), defaultValue, getpropClone); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy