com.google.javascript.jscomp.GatherGetterAndSetterProperties 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.common.collect.ImmutableMap;
import com.google.javascript.jscomp.AccessorSummary.PropertyAccessKind;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Node;
import java.util.LinkedHashMap;
/**
* Finds getter and setter properties in the AST.
*
* Used to back off certain optimizations, e.g. code removal.
*/
public final class GatherGetterAndSetterProperties implements CompilerPass {
private final AbstractCompiler compiler;
GatherGetterAndSetterProperties(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
update(compiler, externs, root);
}
/** Gathers all getters and setters in the AST. */
public static void update(AbstractCompiler compiler, Node externs, Node root) {
// TODO(nickreid): We probably don't need to re-gather from the externs. They don't change so
// the first collection should be good forever.
// For now we traverse both trees every time because there's no reason we have to treat them
// differently.
checkState(externs.getParent() == root.getParent());
compiler.setAccessorSummary(AccessorSummary.create(gather(compiler, externs.getParent())));
}
static ImmutableMap gather(AbstractCompiler compiler, Node root) {
GatherCallback gatherCallback = new GatherCallback();
NodeTraversal.traverse(compiler, root, gatherCallback);
return ImmutableMap.copyOf(gatherCallback.properties);
}
private static final class GatherCallback extends AbstractPostOrderCallback {
private final LinkedHashMap properties = new LinkedHashMap<>();
private void record(String property, PropertyAccessKind kind) {
properties.merge(property, kind, PropertyAccessKind::unionWith);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getToken()) {
case GETTER_DEF:
recordGetterDef(n);
break;
case SETTER_DEF:
recordSetterDef(n);
break;
case CALL:
if (NodeUtil.isObjectDefinePropertyDefinition(n)) {
visitDefineProperty(n);
} else if (NodeUtil.isObjectDefinePropertiesDefinition(n)) {
visitDefineProperties(n);
}
break;
default:
break;
}
}
private void recordGetterDef(Node getterDef) {
checkState(getterDef.isGetterDef());
String name = getterDef.getString();
record(name, PropertyAccessKind.GETTER_ONLY);
}
private void recordSetterDef(Node setterDef) {
checkState(setterDef.isSetterDef());
String name = setterDef.getString();
record(name, PropertyAccessKind.SETTER_ONLY);
}
/**
* Looks for getters and setters passed to Object.defineProperty or Object.defineProperties.
*
* {@code
* Object.defineProperty(obj, 'propertyName', { /* descriptor *\/ });
* Object.defineProperties(obj, { 'propertyName': { /* descriptor *\/ } });
* }
*/
private void visitDescriptor(String propertyName, Node descriptor) {
for (Node key : descriptor.children()) {
if (key.isStringKey() || key.isMemberFunctionDef()) {
if ("get".equals(key.getString())) {
record(propertyName, PropertyAccessKind.GETTER_ONLY);
} else if ("set".equals(key.getString())) {
record(propertyName, PropertyAccessKind.SETTER_ONLY);
}
}
}
}
private void visitDefineProperty(Node definePropertyCall) {
Node propertyNameNode = definePropertyCall.getChildAtIndex(2);
Node descriptor = definePropertyCall.getChildAtIndex(3);
if (!propertyNameNode.isString() || !descriptor.isObjectLit()) {
return;
}
String propertyName = propertyNameNode.getString();
visitDescriptor(propertyName, descriptor);
}
private void visitDefineProperties(Node definePropertiesCall) {
Node props = definePropertiesCall.getChildAtIndex(2);
if (!props.isObjectLit()) {
return;
}
for (Node prop : props.children()) {
if (prop.isStringKey() && prop.hasOneChild() && prop.getFirstChild().isObjectLit()) {
String propertyName = prop.getString();
Node descriptor = prop.getFirstChild();
visitDescriptor(propertyName, descriptor);
}
}
}
}
}