private static boolean attachScopes(AncestorChain<?> ac, ScopeInfo scope) {
// We infect scopes root-wards if we see a problematic construct like
// "eval" or "with" which would change behavior if variables in scope where
// it is declared were renamed.
boolean infected = false;
ParseTreeNode n = ac.node;
n.getAttributes().set(SCOPE, scope);
if (n instanceof FunctionConstructor) {
FunctionConstructor fc = (FunctionConstructor) n;
scope = new ScopeInfo(scope, Scope.fromFunctionConstructor(scope.s, fc));
if (fc.getIdentifierName() != null) {
scope.fns.add(ac.cast(FunctionConstructor.class));
}
// A ctor's name is apparent in its scope, unlike a fn declarations name
// which is apparent in the containing scope.
n.getAttributes().set(SCOPE, scope);
} else if (n instanceof CatchStmt) {
CatchStmt cs = (CatchStmt) n;
scope = new ScopeInfo(scope, Scope.fromCatchStmt(scope.s, cs));
// Normally, declaration in a catch block are hoisted to the parent.
// Since the logic below does that, make sure that the exception
// declaration is not hoisted.
scope.decls.add(AncestorChain.instance(ac, cs.getException()));
cs.getException().getAttributes().set(SCOPE, scope);
// And recurse to the body manually so as to avoid recursing to the
// exception declaration.
attachScopes(AncestorChain.instance(ac, cs.getBody()), scope);
return false;
} else if (n instanceof Reference) {
Reference r = (Reference) n;
String rName = r.getIdentifierName();
Scope definingScope = scope.s.thatDefines(rName);
assert (definingScope != null) || scope.s.isOuter(rName) : rName;
scope.uses.add(new Use(scope.withScope(definingScope), rName));
if ("eval".equals(rName)) { infected = true; }
infected = infected || "eval".equals(rName);
} else if (n instanceof Declaration) {
ScopeInfo declaring = scope;
// Hoist out of catch block scopes.
while (declaring.s.getType() == ScopeType.CATCH) {
declaring = declaring.parent;
}
declaring.decls.add(ac.cast(Declaration.class));
} else if (n instanceof WithStmt) {
// References inside with(...){} could be variable names or they could
// be property names.
infected = true;
} else if (Operation.is(n, Operator.MEMBER_ACCESS)) {
// Do not let the property name reference be treated as a reference to
// a var or global.
attachScopes(AncestorChain.instance(ac, n.children().get(0)), scope);
return false;
}
for (ParseTreeNode child : n.children()) {
infected |= attachScopes(AncestorChain.instance(ac, child), scope);
}
if (infected) { scope.setDynamicUsePossible(); }
return infected;
}