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

com.google.javascript.jscomp.PeepholeCollectPropertyAssignments 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.

There is a newer version: v20240317
Show newest version
/*
 * Copyright 2011 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.rhino.IR;
import com.google.javascript.rhino.Node;

/**
 * A pass that looks for assignments to properties of an object or array
 * immediately following its creation using the abbreviated syntax.
 * 

* E.g. {@code var a = [];a[0] = 0} is optimized to {@code var a = [0]} and * similarly for the object constructor. */ final class PeepholeCollectPropertyAssignments extends AbstractPeepholeOptimization { @Override Node optimizeSubtree(Node subtree) { if (!subtree.isScript() && !subtree.isBlock()) { return subtree; } boolean codeChanged = false; // Look for variable declarations or simple assignments // and start processing there. for (Node child = subtree.getFirstChild(); child != null; child = child.getNext()) { if (!NodeUtil.isNameDeclaration(child) && !NodeUtil.isExprAssign(child)) { continue; } if (!isPropertyAssignmentToName(child.getNext())) { // Quick check to see if there's anything to collapse. continue; } checkState(child.hasOneChild()); Node name = getName(child); if (!name.isName()) { // The assignment target is not a simple name. continue; } Node value = getValue(child); if (value == null || !isInterestingValue(value)) { // No initializer or not an Object or Array literal. continue; } Node propertyCandidate; while ((propertyCandidate = child.getNext()) != null) { // This does not infinitely loop because collectProperty always // removes propertyCandidate from its parent when it returns true. if (!collectProperty(propertyCandidate, name.getString(), value)) { break; } codeChanged = true; } } if (codeChanged) { reportChangeToEnclosingScope(subtree); } return subtree; } private static Node getName(Node n) { if (NodeUtil.isNameDeclaration(n)) { return n.getFirstChild(); } else if (NodeUtil.isExprAssign(n)) { return n.getFirstFirstChild(); } throw new IllegalStateException(); } private static Node getValue(Node n) { if (NodeUtil.isNameDeclaration(n)) { return n.getFirstFirstChild(); } else if (NodeUtil.isExprAssign(n)) { return n.getFirstChild().getLastChild(); } throw new IllegalStateException(); } static boolean isInterestingValue(Node n) { return n.isObjectLit() || n.isArrayLit(); } private static boolean isPropertyAssignmentToName(Node propertyCandidate) { if (propertyCandidate == null) { return false; } // Must be an assignment... if (!NodeUtil.isExprAssign(propertyCandidate)) { return false; } Node expr = propertyCandidate.getFirstChild(); // to a property... Node lhs = expr.getFirstChild(); if (!NodeUtil.isNormalGet(lhs)) { return false; } // of a variable. Node obj = lhs.getFirstChild(); return obj.isName(); } private boolean collectProperty(Node propertyCandidate, String name, Node value) { if (!isPropertyAssignmentToName(propertyCandidate)) { return false; } Node lhs = propertyCandidate.getFirstFirstChild(); // Must be an assignment to the recent variable... if (!name.equals(lhs.getFirstChild().getString())) { return false; } Node rhs = lhs.getNext(); // with a value that cannot change the values of the variables, if (mayHaveSideEffects(rhs) || NodeUtil.canBeSideEffected(rhs)) { return false; } // and does not have a reference to a variable initialized after it. if (!NodeUtil.isLiteralValue(rhs, true) && mightContainForwardReference(rhs, name)) { return false; } switch (value.getToken()) { case ARRAYLIT: if (!collectArrayProperty(value, propertyCandidate)) { return false; } break; case OBJECTLIT: if (!collectObjectProperty(value, propertyCandidate)) { return false; } break; default: throw new IllegalStateException(); } return true; } private static boolean collectArrayProperty(Node arrayLiteral, Node propertyCandidate) { Node assignment = propertyCandidate.getFirstChild(); final int sizeOfArrayAtStart = arrayLiteral.getChildCount(); int maxIndexAssigned = sizeOfArrayAtStart - 1; Node lhs = assignment.getFirstChild(); Node rhs = lhs.getNext(); if (!lhs.isGetElem()) { return false; } Node obj = lhs.getFirstChild(); Node property = obj.getNext(); // The left hand side must have a numeric index if (!property.isNumber()) { return false; } // that is a valid array index double dindex = property.getDouble(); if (!(dindex >= 0) // Handles NaN and negatives. || Double.isInfinite(dindex) || dindex > 0x7fffffffL) { return false; } int index = (int) dindex; if (dindex != index) { return false; } // that would not make the array so sparse that they take more space // when rendered than x[9]=1. if (maxIndexAssigned + 4 < index) { return false; } if (index > maxIndexAssigned) { while (maxIndexAssigned < index - 1) { // Pad the array if it is sparse. // So if array is [0] and integer 3 is assigned at index is 2, then // we want to produce [0,,2]. Node emptyNode = IR.empty().srcref(arrayLiteral); arrayLiteral.addChildToBack(emptyNode); ++maxIndexAssigned; } arrayLiteral.addChildToBack(rhs.detach()); } else { // An out of order assignment. Allow it if it's a hole. Node currentValue = arrayLiteral.getChildAtIndex(index); if (!currentValue.isEmpty()) { // We've already collected a value for this index. return false; } currentValue.replaceWith(rhs.detach()); } propertyCandidate.detach(); return true; } private boolean collectObjectProperty(Node objectLiteral, Node propertyCandidate) { Node assignment = propertyCandidate.getFirstChild(); Node lhs = assignment.getFirstChild(); Node rhs = lhs.getNext(); Node obj = lhs.getFirstChild(); checkState(lhs.isGetProp() || lhs.isGetElem(), lhs); // The property must be statically known. final String propertyName; if (lhs.isGetElem()) { Node property = obj.getNext(); if (property.isNumber()) { propertyName = getSideEffectFreeStringValue(property); } else if (property.isStringLit()) { propertyName = property.getString(); } else { return false; } } else if (lhs.isGetProp()) { propertyName = lhs.getString(); } else { return false; } // Check if the new property already exists in the object literal // Note: Duplicate keys are invalid in strict mode Node existingProperty = null; for (Node currentProperty = objectLiteral.getFirstChild(); currentProperty != null; currentProperty = currentProperty.getNext()) { if (currentProperty.isStringKey() || currentProperty.isMemberFunctionDef()) { // Get the name of the current property String currentPropertyName = currentProperty.getString(); // Get the value of the property Node currentValue = currentProperty.getFirstChild(); // Compare the current property name with the new property name if (currentPropertyName.equals(propertyName)) { existingProperty = currentProperty; // Check if the current value and the new value are side-effect boolean isCurrentValueSideEffect = NodeUtil.canBeSideEffected(currentValue); boolean isNewValueSideEffect = NodeUtil.canBeSideEffected(rhs); // If they are side-effect free then replace the current value with the new one if (isCurrentValueSideEffect || isNewValueSideEffect) { return false; } // Break the loop if the property exists break; } } else if (currentProperty.isGetterDef() || currentProperty.isSetterDef()) { String currentPropertyName = currentProperty.getString(); if (currentPropertyName.equals(propertyName)) { return false; } } } Node newProperty = IR.stringKey(propertyName).srcrefIfMissing(propertyCandidate); // Preserve the quotedness of a property reference if (lhs.isGetElem()) { newProperty.setQuotedStringKey(); } Node newValue = rhs.detach(); newProperty.addChildToBack(newValue); if (existingProperty != null) { deleteNode(existingProperty); } // If the property does not already exist we can safely add it objectLiteral.addChildToBack(newProperty); propertyCandidate.detach(); return true; } private static boolean mightContainForwardReference(Node node, String varName) { if (node.isName()) { return varName.equals(node.getString()); } for (Node child = node.getFirstChild(); child != null; child = child.getNext()) { if (mightContainForwardReference(child, varName)) { return true; } } return false; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy