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

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

/*
 * Copyright 2018 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.checkState;

import com.google.javascript.jscomp.GlobalNamespace.AstChange;
import com.google.javascript.jscomp.GlobalNamespace.Ref;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Set;
import javax.annotation.Nullable;

/**
 * Helper for changing the value of an lvalue in a destructuring pattern. Intended for use by {@link
 * CollapseProperties} and {@link AggressiveInlineAliases} only. This class makes some assumptions
 * that don't generally hold in order to preserve {@link GlobalNamespace} validity and avoid
 * creating temporary variables.
 */
class DestructuringGlobalNameExtractor {
  /**
   * Given an lvalue in a destructuring pattern, and a detached subtree, rewrites the AST to assign
   * the lvalue to the subtree instead of its previous value, while preserving the rest of the
   * destructuring pattern
   *
   * 

For example: given stringKey: 'y' and newName: 'new.name', where 'y' is contained in the * declaration `const {x, y, z} = original;`, this method will produce `const {x} = original; * const y = new.name; const {z} = original;` * *

This method only handles a limited subset of destructuring patterns and is intended only for * CollapseProperties and AggressiveInlineAliases. Preconditions are: * *

    *
  • the pattern must be the lhs of an assignment or declaration (e.g. not a nested pattern) *
  • the original rvalue for the pattern must be an effectively constant qualified name. it is * safe to evaluate the rvalue multiple times. *
* * @param stringKey the STRING_KEY node representing the ref to rewrite, e.g. `y` * @param newName what the lvalue in the STRING_KEY should now be assigned to, e.g. foo.bar * @param newNodes optionally a set to add new AstChanges to, if you need to keep the * GlobalNamespace up-to-date. Otherwise null. * @param ref the Ref corresponding to the `stringKey` */ static void reassignDestructringLvalue( Node stringKey, Node newName, @Nullable Set newNodes, Ref ref, AbstractCompiler compiler) { Node pattern = stringKey.getParent(); Node assignmentType = pattern.getParent(); checkState(assignmentType.isAssign() || assignmentType.isDestructuringLhs(), assignmentType); Node originalRvalue = pattern.getNext(); // e.g. `original` checkState(originalRvalue.isQualifiedName()); // don't handle rvalues with side effects // Create a new assignment using the provided qualified name, e.g. `const y = new.name;` Node lvalueToReassign = stringKey.getOnlyChild().isDefaultValue() ? stringKey.getOnlyChild().getFirstChild() : stringKey.getOnlyChild(); if (newNodes != null) { newNodes.add(new AstChange(ref.module, ref.scope, newName)); } Node rvalue = makeNewRvalueForDestructuringKey(stringKey, newName, newNodes, ref); // Add that new assignment to the AST after the original destructuring pattern if (stringKey.getPrevious() == null && stringKey.getNext() != null) { // Remove the original item completely if the given string key doesn't have any preceding // string keys, /and/ it has succeeding string keys. replaceDestructuringAssignment(pattern, lvalueToReassign.detach(), rvalue); } else { addAfter(pattern, lvalueToReassign.detach(), rvalue); } // Remove any lvalues in the pattern that are after the given stringKey, and put them in // a second destructuring pattern. This is necessary in case they depend on side effects from // assigning `lvalueToReassign`. e.g. create `const {z} = original;` if (stringKey.getNext() != null) { Node newPattern = createNewObjectPatternFromSuccessiveKeys(stringKey).srcref(pattern); // reuse the original rvalue if we don't need it for earlier keys, otherwise make a copy. final Node newRvalue; if (stringKey.getPrevious() == null) { newRvalue = originalRvalue.detach(); } else { newRvalue = originalRvalue.cloneTree(); if (newNodes != null) { newNodes.add(new AstChange(ref.module, ref.scope, newRvalue)); } } addAfter(lvalueToReassign, newPattern, newRvalue); } stringKey.detach(); compiler.reportChangeToEnclosingScope(lvalueToReassign); } /** Adds the new assign or name declaration after the original assign or name declaration */ private static void addAfter(Node originalLvalue, Node newLvalue, Node newRvalue) { Node parent = originalLvalue.getParent(); if (parent.isAssign()) { // create `(, )` Node newAssign = IR.assign(newLvalue, newRvalue).srcref(parent); Node newComma = new Node(Token.COMMA, newAssign); parent.replaceWith(newComma); newComma.addChildToFront(parent); return; } // This must have been in a var/let/const. if (newLvalue.isDestructuringPattern()) { newLvalue = new Node(Token.DESTRUCTURING_LHS, newLvalue, newRvalue).srcref(parent); } else { newLvalue.addChildToBack(newRvalue); } Node declaration = parent.isDestructuringLhs() ? originalLvalue.getGrandparent() : parent; checkState(NodeUtil.isNameDeclaration(declaration), declaration); if (NodeUtil.isStatementParent(declaration.getParent())) { // `const {} = originalRvalue; const newLvalue = newRvalue;` // create an entirely new statement Node newDeclaration = new Node(declaration.getToken()).srcref(declaration); newDeclaration.addChildToBack(newLvalue); declaration.getParent().addChildAfter(newDeclaration, declaration); } else { // `const {} = originalRvalue, newLvalue = newRvalue;` // The Normalize pass tries to ensure name declarations are always in statement blocks, but // currently has not implemented normalization for `for (let x = 0; ...` // so we can't add a new statement declaration.addChildToBack(newLvalue); } } /** * Replaces the given assignment or declaration with the new lvalue/rvalue * * @param pattern a destructuring pattern in an ASSIGN or VAR/LET/CONST */ private static void replaceDestructuringAssignment(Node pattern, Node newLvalue, Node newRvalue) { Node parent = pattern.getParent(); if (parent.isAssign()) { Node newAssign = IR.assign(newLvalue, newRvalue).srcref(parent); parent.replaceWith(newAssign); } else if (newLvalue.isName()) { checkState(parent.isDestructuringLhs()); parent.replaceWith(newLvalue); newLvalue.addChildToBack(newRvalue); } else { pattern.getNext().detach(); pattern.detach(); parent.addChildToBack(newLvalue); parent.addChildToBack(newRvalue); } } /** * Makes a default value expression from the rvalue, or otherwise just returns it * *

e.g. for `const {x = defaultValue} = y;`, and the new rvalue `rvalue`, returns `void 0 === * rvalue ? defaultValue : rvalue` */ private static Node makeNewRvalueForDestructuringKey( Node stringKey, Node rvalue, Set newNodes, Ref ref) { if (stringKey.getOnlyChild().isDefaultValue()) { Node defaultValue = stringKey.getFirstChild().getSecondChild().detach(); // Assume `rvalue` has no side effects since it's a qname, and we can create multiple // references to it. This ignores getters/setters. Node rvalueForSheq = rvalue.cloneTree(); if (newNodes != null) { newNodes.add(new AstChange(ref.module, ref.scope, rvalueForSheq)); } // `void 0 === rvalue ? defaultValue : rvalue` rvalue = IR.hook(IR.sheq(NodeUtil.newUndefinedNode(rvalue), rvalueForSheq), defaultValue, rvalue) .srcrefTree(defaultValue); } return rvalue; } /** Removes any keys after the given key, and adds them in order to a new object pattern */ private static Node createNewObjectPatternFromSuccessiveKeys(Node stringKey) { Node newPattern = stringKey.getParent().cloneNode(); // copies the JSType for (Node next = stringKey.getNext(); next != null; ) { Node newKey = next; next = newKey.getNext(); newPattern.addChildToBack(newKey.detach()); } return newPattern; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy