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

com.google.javascript.jscomp.InlineProperties 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 2012 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.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.ObjectType;
import java.util.HashMap;
import java.util.Map;

/**
 * InlineProperties attempts to find references to properties that are known to be constants and
 * inline the known value.
 *
 * 

This pass relies on type information to find these property references and properties are * assumed to be constant if they are assigned exactly once, unconditionally, in either of the * following contexts: (1) statically on a constructor, or (2) on a class's prototype. * *

The current implementation only inlines immutable values (as defined by * NodeUtil.isImmutableValue). */ final class InlineProperties implements CompilerPass { private final AbstractCompiler compiler; private static class PropertyInfo { PropertyInfo(JSType type, Node value) { this.type = type; this.value = value; } final JSType type; final Node value; } private static final PropertyInfo INVALIDATED = new PropertyInfo(null, null); private final Map props = new HashMap<>(); private final InvalidatingTypes invalidatingTypes; InlineProperties(AbstractCompiler compiler) { this.compiler = compiler; this.invalidatingTypes = new InvalidatingTypes.Builder(compiler.getTypeRegistry()) // TODO(sdh): consider allowing inlining properties of global this // (we already reserve extern'd names, so this should be safe). .disallowGlobalThis() .addTypesInvalidForPropertyRenaming() // NOTE: Mismatches are less important to this pass than to (dis)ambiguate properties. // This pass doesn't remove values (it only inlines them when the type is known), so // it isn't necessary to invalidate due to implicit interface uses. .addAllTypeMismatches(compiler.getTypeMismatches()) .build(); invalidateExternProperties(); } private void invalidateExternProperties() { // Invalidate properties defined in externs. for (String name : compiler.getExternProperties()) { props.put(name, INVALIDATED); } } /** This method gets the JSType from the Node argument and verifies that it is present. */ private JSType getJSType(Node n) { JSType type = n.getJSType(); if (type == null) { return compiler.getTypeRegistry().getNativeType(JSTypeNative.UNKNOWN_TYPE); } else { return type; } } @Override public void process(Node externs, Node root) { // Find and replace the properties in non-extern AST. NodeTraversal.traverse(compiler, root, new GatherCandidates()); NodeTraversal.traverse(compiler, root, new ReplaceCandidates()); } class GatherCandidates extends AbstractPostOrderCallback { @Override public void visit(NodeTraversal t, Node n, Node parent) { // These are assigned at most once in the branches below final boolean invalidatingPropRef; final String propName; if (n.isGetProp()) { propName = n.getLastChild().getString(); if (parent.isAssign()) { invalidatingPropRef = !maybeRecordCandidateDefinition(t, n, parent); } else if (NodeUtil.isLValue(n)) { // Other LValue references invalidate // e.g. in an enhanced for loop or a destructuring statement invalidatingPropRef = true; } else if (parent.isDelProp()) { // Deletes invalidate invalidatingPropRef = true; } else { // A property read doesn't invalidate invalidatingPropRef = false; } } else if ((n.isStringKey() && !n.getParent().isObjectPattern()) || n.isGetterDef() || n.isSetterDef() || n.isMemberFunctionDef()) { propName = n.getString(); // For now, any object literal key invalidates // TODO(johnlenz): support prototype properties like: // foo.prototype = { a: 1, b: 2 }; // TODO(johnlenz): Object.create(), Object.createProperty // and getter/setter defs and member functions also invalidate // since we do not inline functions in this pass // Note that string keys in destructuring patterns are fine, since they just access the prop invalidatingPropRef = true; } else { return; } if (invalidatingPropRef) { checkNotNull(propName); invalidateProperty(propName); } } /** @return Whether this is a valid definition for a candidate property. */ private boolean maybeRecordCandidateDefinition(NodeTraversal t, Node n, Node parent) { checkState(n.isGetProp() && parent.isAssign(), n); Node src = n.getFirstChild(); String propName = n.getLastChild().getString(); Node value = parent.getLastChild(); if (src.isThis()) { // This is a simple assignment like: // this.foo = 1; if (inConstructor(t)) { // This may be a valid assignment. return maybeStoreCandidateValue(getJSType(src), propName, value); } return false; } else if (t.inGlobalHoistScope() && src.isGetProp() && src.getLastChild().getString().equals("prototype")) { // This is a prototype assignment like: // x.prototype.foo = 1; JSType instanceType = maybeGetInstanceTypeFromPrototypeRef(src); if (instanceType != null) { return maybeStoreCandidateValue(instanceType, propName, value); } } else if (t.inGlobalHoistScope()) { // This is a static assignment like: // x.foo = 1; JSType targetType = getJSType(src); if (targetType != null && targetType.isConstructor()) { return maybeStoreCandidateValue(targetType, propName, value); } } return false; } private JSType maybeGetInstanceTypeFromPrototypeRef(Node src) { JSType ownerType = getJSType(src.getFirstChild()); if (ownerType.isConstructor()) { FunctionType functionType = ownerType.toMaybeFunctionType(); return functionType.getInstanceType(); } return null; } private void invalidateProperty(String propName) { props.put(propName, INVALIDATED); } /** * Adds the candidate property to the map if it meets all constness and immutability criteria, * and is not already present in the map. If the property was already present, it is * invalidated. Returns true if the property was successfully added. */ private boolean maybeStoreCandidateValue(JSType type, String propName, Node value) { checkNotNull(value); if (!props.containsKey(propName) && !invalidatingTypes.isInvalidating(type) && NodeUtil.isImmutableValue(value) && NodeUtil.isExecutedExactlyOnce(value)) { props.put(propName, new PropertyInfo(type, value)); return true; } return false; } /** * Returns whether the traversal is directly in an ES6 class constructor or an @constructor * function * *

This returns false for nested functions inside ctors, including arrow functions (even * though the `this` is the same). This pass only cares about property definitions executed once * per ctor invocation, and in general we don't know how many times an arrow fn will be * executed. In the future, we could special case arrow fn IIFEs in this pass if it becomes * useful. */ private boolean inConstructor(NodeTraversal t) { Node root = t.getEnclosingFunction(); if (root == null) { // we might be in the global scope return false; } return NodeUtil.isConstructor(root); } } class ReplaceCandidates extends AbstractPostOrderCallback { @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isGetProp() && !NodeUtil.isLValue(n)) { Node target = n.getFirstChild(); String propName = n.getLastChild().getString(); PropertyInfo info = props.get(propName); if (info != null && info != INVALIDATED && isMatchingType(target, info.type)) { Node replacement = info.value.cloneTree(); if (compiler.getAstAnalyzer().mayHaveSideEffects(n.getFirstChild())) { replacement = IR.comma(n.removeFirstChild(), replacement).srcref(n); } parent.replaceChild(n, replacement); compiler.reportChangeToEnclosingScope(replacement); } } } private boolean isMatchingType(Node n, JSType src) { src = src.restrictByNotNullOrUndefined(); JSType dest = getJSType(n).restrictByNotNullOrUndefined(); if (invalidatingTypes.isInvalidating(dest)) { return false; } if (dest.isConstructor() || src.isConstructor()) { // instead of using .isSubtypeOf for functions, check the prototype chain, since the // FunctionType subtyping semantics is not what we want. // This case is for ES6 class-side inheritance return hasInPrototypeChain(dest.toMaybeFunctionType(), src.toMaybeFunctionType()); } return dest.isSubtypeOf(src); } @SuppressWarnings("ReferenceEquality") private boolean hasInPrototypeChain(FunctionType subCtor, FunctionType superCtor) { if (subCtor == null || superCtor == null) { return false; } ObjectType proto = subCtor; while (proto != null) { if (proto == superCtor) { return true; } proto = proto.getImplicitPrototype(); } return false; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy