OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 package com.google.dart.compiler.backend.js.analysis; | |
6 | |
7 import org.mozilla.javascript.Token; | |
8 import org.mozilla.javascript.ast.AstNode; | |
9 import org.mozilla.javascript.ast.FunctionNode; | |
10 import org.mozilla.javascript.ast.Name; | |
11 import org.mozilla.javascript.ast.NewExpression; | |
12 import org.mozilla.javascript.ast.NodeVisitor; | |
13 import org.mozilla.javascript.ast.PropertyGet; | |
14 import org.mozilla.javascript.ast.Scope; | |
15 import org.mozilla.javascript.ast.Symbol; | |
16 | |
17 import java.util.ArrayList; | |
18 import java.util.HashSet; | |
19 import java.util.List; | |
20 import java.util.Map; | |
21 import java.util.Set; | |
22 | |
23 /** | |
24 * Computes the set of dependencies that a given AstNode node has. | |
25 */ | |
26 class DependencyComputer { | |
27 /** | |
28 * Visitor for determining what dependencies an AstNode has. | |
29 */ | |
30 class DependencyComputingVisitor implements NodeVisitor { | |
31 /** | |
32 * Adds a dependency on the given identifier. If the identifier is virtual
then then a | |
33 * dependency is only added if the enclosing "class" has been instantiated. | |
34 */ | |
35 private void addDependency(String identifier, boolean isVirtual) { | |
36 List<JavascriptElement> members = namesToElements.get(identifier); | |
37 if (members != null) { | |
38 for (JavascriptElement member : members) { | |
39 if (isVirtual && member.isVirtual()) { | |
40 JavascriptElement enclosingElement = member.getEnclosingElement(); | |
41 if (enclosingElement != null && enclosingElement.isInstantiated()) { | |
42 dependencies.add(member); | |
43 } | |
44 } else { | |
45 if (!member.isVirtual()) { | |
46 if (member.getEnclosingElement() != null) { | |
47 dependencies.add(member.getEnclosingElement()); | |
48 } | |
49 } | |
50 dependencies.add(member); | |
51 } | |
52 } | |
53 } | |
54 } | |
55 | |
56 /** | |
57 * Record that we saw a new of a given identifier. | |
58 */ | |
59 private void addInstantiation(String identifier) { | |
60 List<JavascriptElement> instantiatedClasses = namesToElements.get(identifi
er); | |
61 if (instantiatedClasses != null) { | |
62 for (JavascriptElement instantiatedClass : instantiatedClasses) { | |
63 instantiatedClass.setInstantiated(true); | |
64 | |
65 /* | |
66 * Whenever we see an instantiation we must check it and any super typ
e for members that | |
67 * match the virtual names seen to date and we must ensure that the co
rresponding inherits | |
68 * get emitted. | |
69 */ | |
70 while (instantiatedClass != null) { | |
71 JavascriptElement inheritsInvocation = instantiatedClass.getInherits
Invocation(); | |
72 if (inheritsInvocation != null) { | |
73 dependencies.add(inheritsInvocation); | |
74 } | |
75 | |
76 for (JavascriptElement member : instantiatedClass.getMembers()) { | |
77 if (virtualNames.contains(member.getName())) { | |
78 dependencies.add(member); | |
79 } | |
80 } | |
81 | |
82 instantiatedClass = instantiatedClass.getInheritsElement(); | |
83 } | |
84 } | |
85 } | |
86 } | |
87 | |
88 private void addStaticDependency(String identifier) { | |
89 addDependency(identifier, false); | |
90 } | |
91 | |
92 private void addVirtualDependency(String identifier) { | |
93 virtualNames.add(identifier); | |
94 addDependency(identifier, true); | |
95 } | |
96 | |
97 Symbol findSymbol(Scope scope, String name) { | |
98 if (scope == null) { | |
99 return null; | |
100 } | |
101 | |
102 Symbol symbol = scope.getSymbol(name); | |
103 if (symbol == null) { | |
104 Scope parentScope = scope.getParentScope(); | |
105 if (parentScope == null && (scope != scope.getAstRoot())) { | |
106 return findSymbol(scope.getAstRoot(), name); | |
107 } else { | |
108 return findSymbol(parentScope, name); | |
109 } | |
110 } | |
111 | |
112 return symbol; | |
113 } | |
114 | |
115 /** | |
116 * Returns true if the name is a local or parameter name. However, if the n
ame is on the | |
117 * right hand side of a property get then we don't consider this name to be
a local variable. | |
118 */ | |
119 boolean isLocalVariableOrParameter(Name name) { | |
120 Scope definingScope = name.getDefiningScope(); | |
121 Scope enclosingScope = name.getEnclosingScope(); | |
122 if (definingScope != null) { | |
123 Symbol symbol = definingScope.getSymbol(name.getIdentifier()); | |
124 if (definingScope.getType() == Token.FUNCTION) { | |
125 if (symbol != null && (symbol.getDeclType() == Token.VAR) | |
126 || (symbol.getDeclType() == Token.LP)) { | |
127 | |
128 if (name.getParent().getType() == Token.GETPROP) { | |
129 PropertyGet propertyGet = (PropertyGet) name.getParent(); | |
130 return propertyGet.getRight() != name; | |
131 } | |
132 | |
133 return true; | |
134 } | |
135 } | |
136 } | |
137 | |
138 return false; | |
139 } | |
140 | |
141 /** | |
142 * Returns <code>true</code> if the name is associated with a native | |
143 * function or a top level function. | |
144 */ | |
145 private boolean isNativeOrTopLevelFunction(Scope enclosingScope, String name
) { | |
146 List<JavascriptElement> list = namesToElements.get(name); | |
147 if (list == null || list.isEmpty()) { | |
148 return false; | |
149 } | |
150 | |
151 Symbol symbol = findSymbol(enclosingScope, name); | |
152 if (symbol != null | |
153 && (symbol.getDeclType() == Token.FUNCTION || symbol.getDeclType() ==
Token.VAR)) { | |
154 return true; | |
155 } | |
156 | |
157 JavascriptElement javascriptElement = list.get(0); | |
158 return javascriptElement.isNative(); | |
159 } | |
160 | |
161 /** | |
162 * Return a static name if the {@link PropertyGet} matches the pattern x.y o
r | |
163 * x.prototype.y or <code>null</code> if it does not. | |
164 */ | |
165 String maybeGetStaticName(PropertyGet propertyGet) { | |
166 AstNode right = propertyGet.getRight(); | |
167 int rightType = right.getType(); | |
168 if (rightType != Token.NAME) { | |
169 return null; | |
170 } | |
171 | |
172 AstNode left = propertyGet.getLeft(); | |
173 int leftType = left.getType(); | |
174 if (leftType == Token.NAME) { | |
175 String qualifier = ((Name) left).getIdentifier(); | |
176 | |
177 if (isNativeOrTopLevelFunction(left.getEnclosingScope(), qualifier)) { | |
178 String targetName = ((Name) right).getIdentifier(); | |
179 String qualifiedName = qualifier + "." + targetName; | |
180 if ("prototype".equals(targetName)) { | |
181 return qualifiedName; | |
182 } else if (namesToElements.containsKey(qualifiedName)) { | |
183 return qualifiedName; | |
184 } | |
185 } | |
186 } else if (leftType == Token.GETPROP) { | |
187 PropertyGet leftPropGet = (PropertyGet) left; | |
188 String handleSpecialCase = maybeGetStaticName(leftPropGet); | |
189 if (handleSpecialCase != null && handleSpecialCase.endsWith("prototype")
) { | |
190 handleSpecialCase = handleSpecialCase + "." + ((Name) right).getIdenti
fier(); | |
191 if (namesToElements.containsKey(handleSpecialCase)) { | |
192 return handleSpecialCase; | |
193 } | |
194 } | |
195 } | |
196 | |
197 return null; | |
198 } | |
199 | |
200 @Override | |
201 public boolean visit(AstNode node) { | |
202 switch (node.getType()) { | |
203 case Token.GETPROP: | |
204 PropertyGet propertyGet = (PropertyGet) node; | |
205 String staticName = maybeGetStaticName(propertyGet); | |
206 if (staticName != null) { | |
207 addStaticDependency(staticName); | |
208 return false; | |
209 } | |
210 break; | |
211 | |
212 case Token.FUNCTION: | |
213 FunctionNode functionNode = (FunctionNode) node; | |
214 functionNode.getBody().visit(this); | |
215 // Don't process the parameters | |
216 return false; | |
217 | |
218 case Token.NAME: | |
219 Name name = (Name) node; | |
220 if (!isLocalVariableOrParameter(name)) { | |
221 String identifier = name.getIdentifier(); | |
222 addVirtualDependency(identifier); | |
223 } | |
224 break; | |
225 | |
226 case Token.NEW: | |
227 NewExpression newExpression = (NewExpression) node; | |
228 Name target = (Name) newExpression.getTarget(); | |
229 if (target != null) { | |
230 addInstantiation(target.getIdentifier()); | |
231 } | |
232 break; | |
233 | |
234 default: | |
235 break; | |
236 } | |
237 | |
238 return true; | |
239 } | |
240 | |
241 } | |
242 | |
243 private final List<JavascriptElement> dependencies = new ArrayList<JavascriptE
lement>(); | |
244 private final Map<String, List<JavascriptElement>> namesToElements; | |
245 | |
246 /** | |
247 * Names that have been referenced using virtual syntax, i.e. not using A.prot
otype.foo, but | |
248 * as foo or this.foo, etc. | |
249 */ | |
250 private final Set<String> virtualNames = new HashSet<String>(); | |
251 | |
252 public DependencyComputer(Map<String, List<JavascriptElement>> namesToElements
) { | |
253 this.namesToElements = namesToElements; | |
254 } | |
255 | |
256 public List<JavascriptElement> computeDependencies(AstNode node) { | |
257 // Clear the dependencies so this object can be reused. | |
258 dependencies.clear(); | |
259 node.visit(new DependencyComputingVisitor()); | |
260 return dependencies; | |
261 } | |
262 } | |
OLD | NEW |