-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
GatherGetterAndSetterProperties.java
155 lines (131 loc) · 5.18 KB
/
GatherGetterAndSetterProperties.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/*
* 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.
*
* <p>Used to back off certain optimizations, e.g. code removal.
*/
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. */
static void update(AbstractCompiler compiler, Node externs, Node root) {
if (compiler.getOptions().getAssumeGettersAndSettersAreSideEffectFree()) {
return;
}
// 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<String, PropertyAccessKind> 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<String, PropertyAccessKind> 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.
*
* <pre>{@code
* Object.defineProperty(obj, 'propertyName', { /* descriptor *\/ });
* Object.defineProperties(obj, { 'propertyName': { /* descriptor *\/ } });
* }</pre>
*/
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);
}
}
}
}
}