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

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

Go to download

Closure Compiler is a JavaScript optimizing compiler. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls. It is used in many of Google's JavaScript apps, including Gmail, Google Web Search, Google Maps, and Google Docs.

The newest version!
/*
 * 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); // This will ensure that we run this only when features exist in the script 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 - 2024 Weber Informatics LLC | Privacy Policy