| 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 |