This addresses the first half of issue #610, the Obfuscation optimization part.  This work is based on the idea in alex.tkachman's patch, which is to give params and locals the shortest obfuscated names possible.  My implementation follows design discussions with mmendez & tobyr.

Out with the old:
-Every JsNameRef had a JsName
-Obfuscation occurred during the actual printing of the tree
-Catch blocks did not have their own scope
-The "object" scope (contains qualified name references-- ie instance fields/methods) lived under the global, top level "root" scope (contains global functions and variables)

In with the new:
-JsNameRefs can be created in an unresolved state; they contain only simply String idents and have no associated JsName
-JsSymbolResolver resolves all JsNameRefs; if necessary it creates unobfuscatable names in the root scope
-Obfuscation occurs as its own pass, distinct from printing
-Catch blocks have their own scope specifically to contain the catch arg
-"object" scope is now a sibling of the "top" scope; both of which are contained in a mythical "root" scope, which contains only unobfuscatable names

Suggested by: alex.tkachman
Review by: mmendez


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@482 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jdt/FindJsniRefVisitor.java b/dev/core/src/com/google/gwt/dev/jdt/FindJsniRefVisitor.java
index d6a958c..6556c2d 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/FindJsniRefVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/FindJsniRefVisitor.java
@@ -85,7 +85,7 @@
       JsStatements result = jsParser.parse(jsProgram.getScope(), sr, -1);
       result.traverse(new JsAbstractVisitorWithAllVisits() {
         public void endVisit(JsNameRef x) {
-          String ident = x.getName().getIdent();
+          String ident = x.getIdent();
           if (ident.charAt(0) == '@') {
             String className = ident.substring(1, ident.indexOf(':'));
             jsniClasses.add(className);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index 83f68d9..819615f 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -51,11 +51,11 @@
 import com.google.gwt.dev.jjs.impl.TypeMap;
 import com.google.gwt.dev.jjs.impl.TypeTightener;
 import com.google.gwt.dev.jjs.impl.InternalCompilerException.NodeInfo;
-import com.google.gwt.dev.js.FullNamingStrategy;
+import com.google.gwt.dev.js.JsObfuscateNamer;
+import com.google.gwt.dev.js.JsPrettyNamer;
 import com.google.gwt.dev.js.JsSourceGenerationVisitor;
-import com.google.gwt.dev.js.NamingStrategy;
-import com.google.gwt.dev.js.ObfuscatedNamingStrategy;
-import com.google.gwt.dev.js.PrettyNamingStrategy;
+import com.google.gwt.dev.js.JsSymbolResolver;
+import com.google.gwt.dev.js.JsVerboseNamer;
 import com.google.gwt.dev.js.ast.JsProgram;
 import com.google.gwt.dev.util.TextOutputOnPrintWriter;
 import com.google.gwt.dev.util.Util;
@@ -400,18 +400,22 @@
       // (7) Generate a JavaScript code DOM from the Java type declarations
       GenerateJavaScriptAST.exec(jprogram, jsProgram);
 
+      // (8) Resolve all unresolved JsNameRefs
+      JsSymbolResolver.exec(jsProgram);
+      
+      // (9) Obfuscate
+      if (obfuscate) {
+        JsObfuscateNamer.exec(jsProgram);
+      } else if (prettyNames) {
+        JsPrettyNamer.exec(jsProgram);
+      } else {
+        JsVerboseNamer.exec(jsProgram);
+      }
+      
       StringWriter sw = new StringWriter();
       PrintWriter pw = new PrintWriter(sw, true);
       TextOutputOnPrintWriter out = new TextOutputOnPrintWriter(pw, obfuscate);
-      NamingStrategy ns;
-      if (obfuscate) {
-        ns = new ObfuscatedNamingStrategy();
-      } else if (prettyNames) {
-        ns = new PrettyNamingStrategy();
-      } else {
-        ns = new FullNamingStrategy();
-      }
-      JsSourceGenerationVisitor v = new JsSourceGenerationVisitor(out, ns);
+      JsSourceGenerationVisitor v = new JsSourceGenerationVisitor(out);
       jsProgram.traverse(v);
 
       return sw.toString();
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
index e80f39c..5771a5a 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
@@ -165,7 +165,6 @@
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
@@ -181,17 +180,6 @@
 public class GenerateJavaAST {
 
   /**
-   * Comparator for <code>HasName</code> instances.
-   */
-  public static class HasNameSort implements Comparator {
-    public int compare(Object o1, Object o2) {
-      HasName h1 = (HasName) o1;
-      HasName h2 = (HasName) o2;
-      return h1.getName().compareTo(h2.getName());
-    }
-  }
-
-  /**
    * Visit the JDT AST and produce our own AST into the passed-in TypeMap's
    * JProgram. By the end of this pass, the produced AST should contain every
    * piece of information we'll ever need about the code. The JDT nodes should
@@ -1277,7 +1265,7 @@
       func.traverse(new JsAbstractVisitorWithAllVisits() {
         // @Override
         public void endVisit(JsNameRef x) {
-          String ident = x.getName().getIdent();
+          String ident = x.getIdent();
           if (ident.charAt(0) == '@') {
             nameRefs.add(x);
           }
@@ -1287,7 +1275,7 @@
       for (int i = 0; i < nameRefs.size(); ++i) {
         JsNameRef nameRef = (JsNameRef) nameRefs.get(i);
         JSourceInfo info = translateInfo(nameRef.getInfo());
-        String ident = nameRef.getName().getIdent();
+        String ident = nameRef.getIdent();
         HasEnclosingType node = (HasEnclosingType) program.jsniMap.get(ident);
         if (node == null) {
           node = parseJsniRef(info, x, ident);
@@ -2115,7 +2103,7 @@
   }
 
   public static JSourceInfo translateInfo(JsSourceInfo info) {
-    // TODO Auto-generated method stub
+    // TODO implement this
     return null;
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
index ed18bfc..856223d 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -106,7 +106,6 @@
 import com.google.gwt.dev.js.ast.JsNameRef;
 import com.google.gwt.dev.js.ast.JsNew;
 import com.google.gwt.dev.js.ast.JsNode;
-import com.google.gwt.dev.js.ast.JsObfuscatableName;
 import com.google.gwt.dev.js.ast.JsObjectLiteral;
 import com.google.gwt.dev.js.ast.JsParameter;
 import com.google.gwt.dev.js.ast.JsParameters;
@@ -131,6 +130,8 @@
 
 import java.math.BigInteger;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
@@ -144,6 +145,37 @@
  */
 public class GenerateJavaScriptAST {
 
+  private class SortVisitor extends JVisitor {
+
+    private final HasNameSort hasNameSort = new HasNameSort();
+
+    public void endVisit(JClassType x, Context ctx) {
+      Collections.sort(x.fields, hasNameSort);
+
+      // Sort the methods manually to avoid sorting clinit out of place!
+      List methods = x.methods;
+      Object a[] = methods.toArray();
+      Arrays.sort(a, 1, a.length, hasNameSort);
+      for (int i = 1; i < a.length; i++) {
+        methods.set(i, a[i]);
+      }
+    }
+
+    public void endVisit(JInterfaceType x, Context ctx) {
+      Collections.sort(x.fields, hasNameSort);
+      Collections.sort(x.methods, hasNameSort);
+    }
+
+    public void endVisit(JMethod x, Context ctx) {
+      Collections.sort(x.locals, hasNameSort);
+    }
+
+    public void endVisit(JProgram x, Context ctx) {
+      Collections.sort(x.entryMethods, hasNameSort);
+      Collections.sort(x.getDeclaredTypes(), hasNameSort);
+    }
+  }
+
   private class CreateNamesAndScopesVisitor extends JVisitor {
 
     private final Stack/* <JsScope> */scopeStack = new Stack();
@@ -158,9 +190,9 @@
       String name = x.getName();
       String mangleName = mangleName(x);
       if (x.isStatic()) {
-        names.put(x, rootScope.createUniqueObfuscatableName(mangleName, name));
+        names.put(x, topScope.declareName(mangleName, name));
       } else {
-        names.put(x, peek().createUniqueObfuscatableName(mangleName, name));
+        names.put(x, peek().declareName(mangleName, name));
       }
     }
 
@@ -174,14 +206,14 @@
       if (getName(x) != null) {
         return;
       }
-      names.put(x, peek().getOrCreateObfuscatableName(x.getName()));
+      names.put(x, peek().declareName(x.getName()));
     }
 
     // @Override
     public void endVisit(JLocal x, Context ctx) {
       // locals can conflict, that's okay just reuse the same variable
       JsScope scope = peek();
-      JsName jsName = scope.getOrCreateObfuscatableName(x.getName());
+      JsName jsName = scope.declareName(x.getName());
       names.put(x, jsName);
     }
 
@@ -192,40 +224,39 @@
 
     // @Override
     public void endVisit(JParameter x, Context ctx) {
-      names.put(x, peek().createUniqueObfuscatableName(x.getName()));
+      names.put(x, peek().declareName(x.getName()));
     }
 
     // @Override
     public void endVisit(JProgram x, Context ctx) {
       // visit special things that may have been culled
       JField field = x.getSpecialField("Object.typeId");
-      names.put(field, objectScope.getOrCreateObfuscatableName(
-          mangleName(field), field.getName()));
-
-      field = x.getSpecialField("Object.typeName");
-      names.put(field,
-          objectScope.getOrCreateObfuscatableName(mangleName(field)));
-
-      field = x.getSpecialField("Cast.typeIdArray");
-      names.put(field, rootScope.getOrCreateObfuscatableName(mangleName(field),
+      names.put(field, objectScope.declareName(mangleName(field),
           field.getName()));
 
+      field = x.getSpecialField("Object.typeName");
+      names.put(field, objectScope.declareName(mangleName(field),
+          field.getName()));
+
+      field = x.getSpecialField("Cast.typeIdArray");
+      names.put(field, topScope.declareName(mangleName(field), field.getName()));
+
       /*
-       * put the null method and field into fObjectScope since they can be
+       * put the null method and field into objectScope since they can be
        * referenced as instance on null-types (as determined by type flow)
        */
       JMethod nullMethod = x.getNullMethod();
       polymorphicNames.put(nullMethod,
-          objectScope.createUniqueObfuscatableName(nullMethod.getName()));
+          objectScope.declareName(nullMethod.getName()));
       JField nullField = x.getNullField();
-      JsName nullFieldName = objectScope.createUniqueObfuscatableName(nullField.getName());
+      JsName nullFieldName = objectScope.declareName(nullField.getName());
       polymorphicNames.put(nullField, nullFieldName);
       names.put(nullField, nullFieldName);
 
       /*
        * put nullMethod in the global scope, too; it's the replacer for clinits
        */
-      nullMethodName = rootScope.createUniqueObfuscatableName(nullMethod.getName());
+      nullMethodName = topScope.declareName(nullMethod.getName());
       names.put(nullMethod, nullMethodName);
     }
 
@@ -236,7 +267,7 @@
 
     // @Override
     public boolean visit(JClassType x, Context ctx) {
-      // have I already been visited as a supertype?
+      // have I already been visited as a super type?
       JsScope myScope = (JsScope) classScopes.get(x);
       if (myScope != null) {
         push(myScope);
@@ -244,8 +275,7 @@
       }
 
       // My seed function name
-      names.put(x, rootScope.createUniqueObfuscatableName(getNameString(x),
-          x.getShortName()));
+      names.put(x, topScope.declareName(getNameString(x), x.getShortName()));
 
       // My class scope
       if (x.extnds == null) {
@@ -266,8 +296,7 @@
         if (parentScope == objectScope) {
           parentScope = interfaceScope;
         }
-        myScope = new JsScope(parentScope);
-        myScope.setDescription("class " + x.getShortName());
+        myScope = new JsScope(parentScope, "class " + x.getShortName());
       }
       classScopes.put(x, myScope);
 
@@ -290,8 +319,7 @@
       if (!x.isStatic()) {
         if (getPolyName(x) == null) {
           String mangleName = mangleNameForPoly(x);
-          JsName polyName = interfaceScope.getOrCreateObfuscatableName(
-              mangleName, name);
+          JsName polyName = interfaceScope.declareName(mangleName, name);
           polymorphicNames.put(x, polyName);
         }
       }
@@ -305,15 +333,15 @@
       // my global name
       JsName globalName;
       if (x.getEnclosingType() == null) {
-        globalName = rootScope.createUniqueObfuscatableName(name);
+        globalName = topScope.declareName(name);
       } else {
         String mangleName = mangleNameForGlobal(x);
-        globalName = rootScope.createUniqueObfuscatableName(mangleName, name);
+        globalName = topScope.declareName(mangleName, name);
       }
       names.put(x, globalName);
 
       // create my peer JsFunction
-      JsFunction jsFunction = new JsFunction(rootScope, globalName);
+      JsFunction jsFunction = new JsFunction(topScope, globalName);
       methodMap.put(x, jsFunction);
 
       push(jsFunction.getScope());
@@ -327,21 +355,45 @@
       if (!x.isStatic()) {
         if (getPolyName(x) == null) {
           String mangleName = mangleNameForPoly(x);
-          JsName polyName = interfaceScope.getOrCreateObfuscatableName(
-              mangleName, name);
+          JsName polyName = interfaceScope.declareName(mangleName, name);
           polymorphicNames.put(x, polyName);
         }
       }
 
       // set my global name now that we have a name allocator
       String fnName = mangleNameForGlobal(x);
-      JsName globalName = rootScope.createUniqueObfuscatableName(fnName, name);
+      JsName globalName = topScope.declareName(fnName, name);
       x.getFunc().setName(globalName);
       names.put(x, globalName);
 
       return false;
     }
 
+    public boolean visit(JTryStatement x, Context ctx) {
+      accept(x.getTryBlock());
+
+      List catchArgs = x.getCatchArgs();
+      List catchBlocks = x.getCatchBlocks();
+      for (int i = 0, c = catchArgs.size(); i < c; ++i) {
+        JLocalRef arg = (JLocalRef) catchArgs.get(i);
+        JBlock catchBlock = (JBlock) catchBlocks.get(i);
+        JsCatch jsCatch = new JsCatch(peek(), arg.getTarget().getName());
+        JsParameter jsParam = jsCatch.getParameter();
+        names.put(arg.getTarget(), jsParam.getName());
+        catchMap.put(catchBlock, jsCatch);
+
+        push(jsCatch.getScope());
+        accept(catchBlock);
+        pop();
+      }
+
+      // TODO: normalize this so it's never null?
+      if (x.getFinallyBlock() != null) {
+        accept(x.getFinallyBlock());
+      }
+      return false;
+    }
+
     private JsScope peek() {
       return (JsScope) scopeStack.peek();
     }
@@ -361,13 +413,18 @@
 
     private JMethod currentMethod = null;
 
-    private final JsName globalTemp = rootScope.getOrCreateUnobfuscatableName("_");
+    private final JsName globalTemp = topScope.declareName("_");
 
     private final Stack/* <JsNode> */nodeStack = new Stack/* <JsNode> */();
 
-    private final JsName prototype = objectScope.getOrCreateUnobfuscatableName("prototype");
+    private final JsName prototype = objectScope.declareName("prototype");
 
-    private final JsName window = rootScope.getOrCreateUnobfuscatableName("window");
+    private final JsName window = jsProgram.getRootScope().declareName("window");
+
+    {
+      globalTemp.setObfuscatable(false);
+      prototype.setObfuscatable(false);
+    }
 
     // @Override
     public void endVisit(JAbsentArrayDimension x, Context ctx) {
@@ -466,7 +523,7 @@
     public void endVisit(JClassLiteral x, Context ctx) {
       // My seed function name
       String nameString = x.getRefType().getJavahSignatureName() + "_classlit";
-      JsName classLit = rootScope.getOrCreateObfuscatableName(nameString);
+      JsName classLit = topScope.declareName(nameString);
       classLits.put(x.getRefType(), classLit);
       push(classLit.makeRef());
     }
@@ -508,7 +565,7 @@
           JsName seedFuncName = getName(x);
 
           // seed function
-          JsFunction seedFunc = new JsFunction(rootScope, seedFuncName);
+          JsFunction seedFunc = new JsFunction(topScope, seedFuncName);
           JsBlock body = new JsBlock();
           seedFunc.setBody(body);
           globalStmts.add(seedFunc.makeStmt());
@@ -533,7 +590,7 @@
            * primitive with a modified prototype.
            */
           JsNameRef rhs = prototype.makeRef();
-          rhs.setQualifier(rootScope.getOrCreateUnobfuscatableName("String").makeRef());
+          rhs.setQualifier(jsProgram.getRootScope().declareName("String").makeRef());
           JsExpression tmpAsg = createAssignment(globalTemp.makeRef(), rhs);
           globalStmts.add(tmpAsg.makeStmt());
         }
@@ -557,7 +614,8 @@
             // _.toString = function(){return this.java_lang_Object_toString();}
 
             // lhs
-            JsName lhsName = rootScope.getOrCreateUnobfuscatableName("toString");
+            JsName lhsName = objectScope.declareName("toString");
+            lhsName.setObfuscatable(false);
             JsNameRef lhs = lhsName.makeRef();
             lhs.setQualifier(globalTemp.makeRef());
 
@@ -567,7 +625,7 @@
             toStringRef.setQualifier(new JsThisRef());
             call.setQualifier(toStringRef);
             JsReturn jsReturn = new JsReturn(call);
-            JsFunction rhs = new JsFunction(rootScope);
+            JsFunction rhs = new JsFunction(topScope);
             JsBlock body = new JsBlock();
             body.getStatements().add(jsReturn);
             rhs.setBody(body);
@@ -813,7 +871,7 @@
       if (x.getInitializer() == null) {
         pop(); // localRef
         /*
-         * local decls can only appear in blocks, so it's ok to push null
+         * local decls can only appear in blocks, so it's okay to push null
          * instead of an empty statement
          */
         push(null);
@@ -875,8 +933,7 @@
       JsVars vars = new JsVars();
       Set alreadySeen = new HashSet();
       for (int i = 0; i < locals.size(); ++i) {
-        JsNameRef localRef = (JsNameRef) locals.get(i);
-        JsName name = localRef.getName();
+        JsName name = (JsName) names.get(x.locals.get(i));
         String ident = name.getIdent();
         if (!alreadySeen.contains(ident)) {
           alreadySeen.add(ident);
@@ -915,14 +972,16 @@
         if (x.isStaticDispatchOnly()) {
           /*
            * Dispatch statically (odd case). This happens when a call that must
-           * be static is targetting an instance method that could not be
+           * be static is targeting an instance method that could not be
            * transformed into a static. For example, making a super call into a
            * native method currently causes this, because we cannot currently
            * staticify native methods.
            * 
            * Have to use a "call" construct.
            */
-          qualifier = objectScope.getOrCreateUnobfuscatableName("call").makeRef();
+          JsName callName = objectScope.declareName("call");
+          callName.setObfuscatable(false);
+          qualifier = callName.makeRef();
           qualifier.setQualifier(getName(method).makeRef());
           jsInvocation.getArguments().add(0, (JsExpression) pop()); // instance
         } else {
@@ -992,7 +1051,7 @@
 
       // types don't push
 
-      List/* <JsFunction> */funcs = popList(x.entryMethods.size()); // entrymethods
+      List/* <JsFunction> */funcs = popList(x.entryMethods.size()); // entryMethods
       for (int i = 0; i < funcs.size(); ++i) {
         JsFunction func = (JsFunction) funcs.get(i);
         if (func != null) {
@@ -1015,15 +1074,17 @@
        * }
        * </pre>
        */
-      JsFunction gwtOnLoad = new JsFunction(rootScope);
+      JsFunction gwtOnLoad = new JsFunction(topScope);
       globalStmts.add(gwtOnLoad.makeStmt());
-      gwtOnLoad.setName(rootScope.getOrCreateUnobfuscatableName("gwtOnLoad"));
+      JsName gwtOnLoadName = topScope.declareName("gwtOnLoad");
+      gwtOnLoadName.setObfuscatable(false);
+      gwtOnLoad.setName(gwtOnLoadName);
       JsBlock body = new JsBlock();
       gwtOnLoad.setBody(body);
       JsScope fnScope = gwtOnLoad.getScope();
       JsParameters params = gwtOnLoad.getParameters();
-      JsObfuscatableName errFn = fnScope.createUniqueObfuscatableName("errFn");
-      JsObfuscatableName modName = fnScope.createUniqueObfuscatableName("modName");
+      JsName errFn = fnScope.declareName("errFn");
+      JsName modName = fnScope.declareName("modName");
       params.add(new JsParameter(errFn));
       params.add(new JsParameter(modName));
       JsIf jsIf = new JsIf();
@@ -1042,7 +1103,7 @@
           callBlock.getStatements().add(call.makeStmt());
         }
       }
-      JsCatch jsCatch = new JsCatch(fnScope.createUniqueObfuscatableName("e"));
+      JsCatch jsCatch = new JsCatch(fnScope, "e");
       jsTry.getCatches().add(jsCatch);
       JsBlock catchBlock = new JsBlock();
       jsCatch.setBody(catchBlock);
@@ -1097,11 +1158,11 @@
     public void endVisit(JsniMethod x, Context ctx) {
       JsFunction jsFunc = x.getFunc();
 
-      // replace all jsni idents with a real JsName now that we know it
+      // replace all JSNI idents with a real JsName now that we know it
       jsFunc.traverse(new JsAbstractVisitorWithAllVisits() {
         // @Override
         public void endVisit(JsNameRef x) {
-          String ident = x.getName().getIdent();
+          String ident = x.getIdent();
           if (ident.charAt(0) == '@') {
             HasEnclosingType node = (HasEnclosingType) program.jsniMap.get(ident);
             assert (node != null);
@@ -1109,7 +1170,7 @@
               JField field = (JField) node;
               JsName jsName = getName(field);
               assert (jsName != null);
-              x.setName(jsName);
+              x.resolve(jsName);
               JsInvocation clinitCall = maybeCreateClinitCall(field);
               if (clinitCall != null) {
                 assert (x.getQualifier() == null);
@@ -1120,7 +1181,7 @@
               if (x.getQualifier() == null) {
                 JsName jsName = getName(method);
                 assert (jsName != null);
-                x.setName(jsName);
+                x.resolve(jsName);
               } else {
                 JsName jsName = getPolyName(method);
                 if (jsName == null) {
@@ -1128,7 +1189,7 @@
                   // type that was never actually instantiated.
                   jsName = nullMethodName;
                 }
-                x.setName(jsName);
+                x.resolve(jsName);
               }
             }
           }
@@ -1195,8 +1256,8 @@
       assert (size < 2 && size == x.getCatchBlocks().size());
       if (size == 1) {
         JsBlock catchBlock = (JsBlock) pop(); // catchBlocks
-        JsNameRef arg = (JsNameRef) pop(); // catchArgs
-        JsCatch jsCatch = new JsCatch(arg.getName());
+        pop(); // catchArgs
+        JsCatch jsCatch = (JsCatch) catchMap.get(x.getCatchBlocks().get(0));
         jsCatch.setBody(catchBlock);
         jsTry.getCatches().add(jsCatch);
       }
@@ -1224,7 +1285,7 @@
         return false;
       }
 
-      // force supertype to generate code first, this is required for prototype
+      // force super type to generate code first, this is required for prototype
       // chaining to work properly
       if (x.extnds != null && !alreadyRan.contains(x)) {
         accept(x.extnds);
@@ -1246,7 +1307,7 @@
       JsReturn jsReturn = new JsReturn(window.makeRef());
       JsBlock body = new JsBlock();
       body.getStatements().add(jsReturn);
-      JsFunction nullFunc = new JsFunction(rootScope, nullMethodName);
+      JsFunction nullFunc = new JsFunction(topScope, nullMethodName);
       nullFunc.setBody(body);
       jsProgram.getGlobalBlock().getStatements().add(nullFunc.makeStmt());
       return true;
@@ -1260,7 +1321,7 @@
 
     public boolean visit(JSwitchStatement x, Context ctx) {
       /*
-       * What a pain.. JSwitchStatement and JsSwitch are modelled completely
+       * What a pain.. JSwitchStatement and JsSwitch are modeled completely
        * differently. Here we try to resolve those differences.
        */
       JsSwitch jsSwitch = new JsSwitch();
@@ -1309,7 +1370,7 @@
     }
 
     private void handleClinit(JsFunction clinitFunc) {
-      // self-assign to the null func immediately (to prevent reentrancy)
+      // self-assign to the null method immediately (to prevent reentrancy)
       JsExpression asg = createAssignment(clinitFunc.getName().makeRef(),
           nullMethodName.makeRef());
       clinitFunc.getBody().getStatements().add(0, asg.makeStmt());
@@ -1461,36 +1522,45 @@
     generateJavaScriptAST.execImpl();
   }
 
+  private final Map/* <JBlock, JsCatch> */catchMap = new IdentityHashMap();
   private final Map/* <JType, JsName> */classLits = new IdentityHashMap();
-
   private final Map/* <JClassType, JsScope> */classScopes = new IdentityHashMap();
 
+  /**
+   * Contains JsNames for all interface methods. A special scope is needed so
+   * that independent classes will obfuscate their interface implementation
+   * methods the same way.
+   */
   private final JsScope interfaceScope;
 
   private final JsProgram jsProgram;
-
   private final Map/* <JMethod, JsFunction> */methodMap = new IdentityHashMap();
-
   private final Map/* <HasName, JsName> */names = new IdentityHashMap();
-
   private JsName nullMethodName;
 
+  /**
+   * Contains JsNames for the Object instance methods, such as equals, hashCode,
+   * and toString. All other class scopes have this scope as an ultimate parent.
+   */
   private final JsScope objectScope;
 
   private final Map/* <JMethod, JsName> */polymorphicNames = new IdentityHashMap();
   private final JProgram program;
-  private final JsScope rootScope;
+
+  /**
+   * Contains JsNames for all globals, such as static fields and methods.
+   */
+  private final JsScope topScope;
+
   private final JTypeOracle typeOracle;
 
   private GenerateJavaScriptAST(JProgram program, JsProgram jsProgram) {
     this.program = program;
     typeOracle = program.typeOracle;
     this.jsProgram = jsProgram;
-    rootScope = jsProgram.getScope();
-    objectScope = new JsScope(rootScope);
-    objectScope.setDescription("Object scope");
-    interfaceScope = new JsScope(objectScope);
-    interfaceScope.setDescription("Interfaces");
+    topScope = jsProgram.getScope();
+    objectScope = jsProgram.getObjectScope();
+    interfaceScope = new JsScope(objectScope, "Interfaces");
   }
 
   String getNameString(HasName hasName) {
@@ -1523,6 +1593,8 @@
   }
 
   private void execImpl() {
+    SortVisitor sorter = new SortVisitor();
+    sorter.accept(program);
     CreateNamesAndScopesVisitor creator = new CreateNamesAndScopesVisitor();
     creator.accept(program);
     GenerateJavaScriptVisitor generator = new GenerateJavaScriptVisitor();
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/HasScope.java b/dev/core/src/com/google/gwt/dev/jjs/impl/HasNameSort.java
similarity index 61%
rename from dev/core/src/com/google/gwt/dev/js/ast/HasScope.java
rename to dev/core/src/com/google/gwt/dev/jjs/impl/HasNameSort.java
index dbb3ba5..c19e57f 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/HasScope.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/HasNameSort.java
@@ -13,11 +13,19 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.dev.js.ast;
+package com.google.gwt.dev.jjs.impl;
+
+import com.google.gwt.dev.jjs.ast.HasName;
+
+import java.util.Comparator;
 
 /**
- * Implemented by JavaScript language objects that have scope.
+ * Comparator for <code>HasName</code> instances.
  */
-public interface HasScope {
-  JsScope getScope();
+public class HasNameSort implements Comparator {
+  public int compare(Object o1, Object o2) {
+    HasName h1 = (HasName) o1;
+    HasName h2 = (HasName) o2;
+    return h1.getName().compareTo(h2.getName());
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/js/FullNamingStrategy.java b/dev/core/src/com/google/gwt/dev/js/FullNamingStrategy.java
deleted file mode 100644
index fd54afc..0000000
--- a/dev/core/src/com/google/gwt/dev/js/FullNamingStrategy.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2006 Google Inc.
- * 
- * 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.gwt.dev.js;
-
-import com.google.gwt.dev.js.ast.JsObfuscatableName;
-import com.google.gwt.dev.js.ast.JsScope;
-
-/**
- * Implements a naming strategy that does not alter the standard names of
- * identifiers.
- */
-public class FullNamingStrategy extends NamingStrategy {
-
-  // @Override
-  protected String getBaseIdent(JsObfuscatableName name) {
-    return name.getIdent();
-  }
-
-  // @Override
-  protected String obfuscate(String name, JsScope scope, JsScope rootScope) {
-    return name;
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsKeywords.java b/dev/core/src/com/google/gwt/dev/js/JsKeywords.java
index 2cae6d1..465676f 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsKeywords.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsKeywords.java
@@ -15,23 +15,21 @@
  */
 package com.google.gwt.dev.js;
 
-import com.google.gwt.dev.util.CharArrayComparator;
-
-import java.util.TreeSet;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * Determines whether or not a particular string is a JavaScript keyword or not.
  */
 public class JsKeywords {
 
-  private static TreeSet sJavaScriptKeywords = new TreeSet(
-      CharArrayComparator.INSTANCE);
+  private static Set sJavaScriptKeywords = new HashSet();
 
   static {
     initJavaScriptKeywords();
   }
 
-  public static boolean isKeyword(char[] s) {
+  public static boolean isKeyword(String s) {
     return sJavaScriptKeywords.contains(s);
   }
 
@@ -60,7 +58,7 @@
         "const", "float", "long", "short", "volatile"};
 
     for (int i = 0; i < keywords.length; i++) {
-      sJavaScriptKeywords.add(keywords[i].toCharArray());
+      sJavaScriptKeywords.add(keywords[i]);
     }
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/js/JsObfuscateNamer.java b/dev/core/src/com/google/gwt/dev/js/JsObfuscateNamer.java
new file mode 100644
index 0000000..8875e4e
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/js/JsObfuscateNamer.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2006 Google Inc.
+ * 
+ * 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.gwt.dev.js;
+
+import com.google.gwt.dev.js.ast.JsName;
+import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.js.ast.JsRootScope;
+import com.google.gwt.dev.js.ast.JsScope;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A namer that uses short, unrecognizable idents to minimize generated code
+ * size.
+ */
+public class JsObfuscateNamer {
+
+  /**
+   * A lookup table of base-64 chars we use to encode idents.
+   */
+  private static final char[] sBase64Chars = new char[] {
+      'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+      'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B',
+      'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+      'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3',
+      '4', '5', '6', '7', '8', '9', '$', '_'};
+
+  /**
+   * A temp buffer big enough to hold at least 32 bits worth of base-64 chars.
+   */
+  private static final char[] sIdentBuf = new char[6];
+
+  public static void exec(JsProgram program) {
+    new JsObfuscateNamer(program).execImpl();
+  }
+
+  private static String makeObfuscatedIdent(int id) {
+    // Use base-32 for the first character of the identifier,
+    // so that we don't use any numbers (which are illegal at
+    // the beginning of an identifier).
+    //
+    int i = 0;
+    sIdentBuf[i++] = sBase64Chars[id & 0x1f];
+    id >>= 5;
+
+    // Use base-64 for the rest of the identifier.
+    //
+    while (id != 0) {
+      sIdentBuf[i++] = sBase64Chars[id & 0x3f];
+      id >>= 6;
+    }
+
+    return new String(sIdentBuf, 0, i);
+  }
+
+  /**
+   * Communicates to a parent scope the maximum id used by any of its children.
+   */
+  private int maxChildId = 0;
+  private final JsProgram program;
+
+  public JsObfuscateNamer(JsProgram program) {
+    this.program = program;
+  }
+
+  private void execImpl() {
+    visit(program.getRootScope());
+  }
+
+  private boolean isLegal(JsScope scope, String newIdent) {
+    if (JsKeywords.isKeyword(newIdent)) {
+      return false;
+    }
+    /*
+     * Never obfuscate a name into an identifier that conflicts with an existing
+     * unobfuscatable name! It's okay if it conflicts with an existing
+     * obfuscatable name, since that name will get obfuscated to something else
+     * anyway.
+     */
+    return (scope.findExistingUnobfuscatableName(newIdent) == null);
+  }
+
+  private void visit(JsScope scope) {
+    // Save off the maxChildId which is currently being computed for my parent.
+    int mySiblingsMaxId = maxChildId;
+
+    /*
+     * Visit my children first. Reset maxChildId so  that my children will get
+     * a clean slate: I do not communicate to my children.
+     */
+    maxChildId = 0;
+    List children = scope.getChildren();
+    for (Iterator it = children.iterator(); it.hasNext();) {
+      visit((JsScope) it.next());
+    }
+    // maxChildId is now the max of all of my children's ids
+
+    JsRootScope rootScope = program.getRootScope();
+    if (scope == rootScope) {
+      return;
+    }
+
+    // Visit my idents.
+    int curId = maxChildId;
+    for (Iterator it = scope.getAllNames(); it.hasNext();) {
+      JsName name = (JsName) it.next();
+      if (!name.isObfuscatable()) {
+        // Unobfuscatable names become themselves.
+        name.setShortIdent(name.getIdent());
+        continue;
+      }
+
+      String newIdent;
+      while (true) {
+        // Get the next possible obfuscated name
+        newIdent = makeObfuscatedIdent(curId++);
+        if (isLegal(scope, newIdent)) {
+          break;
+        }
+      }
+      name.setShortIdent(newIdent);
+    }
+
+    maxChildId = Math.max(mySiblingsMaxId, curId);
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsParser.java b/dev/core/src/com/google/gwt/dev/js/JsParser.java
index be3fcfb..0c7a9d4 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsParser.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsParser.java
@@ -44,6 +44,7 @@
 import com.google.gwt.dev.js.ast.JsParameter;
 import com.google.gwt.dev.js.ast.JsPostfixOperation;
 import com.google.gwt.dev.js.ast.JsPrefixOperation;
+import com.google.gwt.dev.js.ast.JsProgram;
 import com.google.gwt.dev.js.ast.JsPropertyInitializer;
 import com.google.gwt.dev.js.ast.JsRegExp;
 import com.google.gwt.dev.js.ast.JsReturn;
@@ -77,6 +78,7 @@
  */
 public class JsParser {
 
+  private JsProgram program;
   private final Stack scopeStack = new Stack();
 
   public JsParser() {
@@ -112,6 +114,7 @@
 
       // Map the Rhino AST to ours.
       //
+      program = scope.getProgram();
       pushScope(scope);
       JsStatements stmts = mapStatements(topNode);
       popScope();
@@ -216,7 +219,7 @@
         return mapName(node);
 
       case TokenStream.STRING:
-        return getScope().getProgram().getStringLiteral(node.getString());
+        return program.getStringLiteral(node.getString());
 
       case TokenStream.NUMBER:
         return mapNumber(node);
@@ -334,8 +337,7 @@
     if (unknown instanceof JsStringLiteral) {
       JsStringLiteral lit = (JsStringLiteral) unknown;
       String litName = lit.getValue();
-      JsName name = getScope().getOrCreateUnobfuscatableName(litName);
-      return name.makeRef();
+      return new JsNameRef(litName);
     } else {
       throw createParserException("Expecting a name reference", nameRefNode);
     }
@@ -461,7 +463,7 @@
   private JsStatement mapDebuggerStatement() {
     // Calls an optional method to invoke the debugger.
     //
-    return getScope().getProgram().getDebuggerStmt();
+    return program.getDebuggerStmt();
   }
 
   private JsNode mapDeleteProp(Node node) throws JsParserException {
@@ -571,7 +573,7 @@
         //
         Node fromIterVarName = fromIter.getFirstChild();
         String fromName = fromIterVarName.getString();
-        JsName toName = getScope().getOrCreateObfuscatableName(fromName);
+        JsName toName = getScope().declareName(fromName);
         toForIn = new JsForIn(toName);
         Node fromIterInit = fromIterVarName.getFirstChild();
         if (fromIterInit != null) {
@@ -593,7 +595,7 @@
       if (bodyStmt != null) {
         toForIn.setBody(bodyStmt);
       } else {
-        toForIn.setBody(getScope().getProgram().getEmptyStmt());
+        toForIn.setBody(program.getEmptyStmt());
       }
 
       return toForIn;
@@ -620,7 +622,7 @@
       if (bodyStmt != null) {
         toFor.setBody(bodyStmt);
       } else {
-        toFor.setBody(getScope().getProgram().getEmptyStmt());
+        toFor.setBody(program.getEmptyStmt());
       }
       return toFor;
     }
@@ -637,7 +639,7 @@
     String fromFnName = fromFnNameNode.getString();
     JsName toFnName = null;
     if (fromFnName != null && fromFnName.length() > 0) {
-      toFnName = getScope().getOrCreateObfuscatableName(fromFnName);
+      toFnName = getScope().declareName(fromFnName);
     }
 
     // Create it, and set the params.
@@ -652,8 +654,7 @@
     while (fromParamNode != null) {
       String fromParamName = fromParamNode.getString();
       // should this be unique? I think not since you can have dup args.
-      JsName paramName = toFn.getScope().getOrCreateObfuscatableName(
-          fromParamName);
+      JsName paramName = toFn.getScope().declareName(fromParamName);
       toFn.getParameters().add(new JsParameter(paramName));
       fromParamNode = fromParamNode.getNext();
     }
@@ -693,7 +694,7 @@
       //
       Object obj = getPropNode.getProp(Node.SPECIAL_PROP_PROP);
       assert (obj instanceof String);
-      toNameRef = getScope().getOrCreateUnobfuscatableName((String) obj).makeRef();
+      toNameRef = new JsNameRef((String) obj);
     }
     toNameRef.setQualifier(toQualifier);
 
@@ -745,7 +746,7 @@
 
   private JsLabel mapLabel(Node labelNode) throws JsParserException {
     String fromName = labelNode.getFirstChild().getString();
-    JsName toName = getScope().getOrCreateObfuscatableName(fromName);
+    JsName toName = getScope().declareName(fromName);
     Node fromStmt = labelNode.getFirstChild().getNext();
     JsLabel toLabel = new JsLabel(toName);
     toLabel.setStmt(mapStatement(fromStmt));
@@ -758,14 +759,7 @@
    */
   private JsNameRef mapName(Node node) {
     String ident = node.getString();
-
-    JsName name = getScope().findExistingName(ident);
-    if (name == null) {
-      // This is a name in the ether, so we cannot obfuscate it.
-      //
-      name = getScope().getOrCreateUnobfuscatableName(ident);
-    }
-    return name.makeRef();
+    return new JsNameRef(ident);
   }
 
   private JsNew mapNew(Node newNode) throws JsParserException {
@@ -794,9 +788,9 @@
     double x = numberNode.getDouble();
     long j = (long) x;
     if (x == j) {
-      return getScope().getProgram().getIntegralLiteral(BigInteger.valueOf(j));
+      return program.getIntegralLiteral(BigInteger.valueOf(j));
     } else {
-      return getScope().getProgram().getDecimalLiteral(String.valueOf(x));
+      return program.getDecimalLiteral(String.valueOf(x));
     }
   }
 
@@ -863,13 +857,13 @@
         return new JsThisRef();
 
       case TokenStream.TRUE:
-        return getScope().getProgram().getTrueLiteral();
+        return program.getTrueLiteral();
 
       case TokenStream.FALSE:
-        return getScope().getProgram().getFalseLiteral();
+        return program.getFalseLiteral();
 
       case TokenStream.NULL:
-        return getScope().getProgram().getNullLiteral();
+        return program.getNullLiteral();
 
       default:
         throw new JsParserException("Unknown primary: " + node.getIntDatum());
@@ -984,7 +978,7 @@
     } else {
       // When map() returns null, we return an empty statement.
       //
-      return getScope().getProgram().getEmptyStmt();
+      return program.getEmptyStmt();
     }
   }
 
@@ -1084,9 +1078,7 @@
       // Map the catch variable.
       //
       Node fromCatchVarName = fromCatchNode.getFirstChild();
-      JsName name = getScope().getOrCreateObfuscatableName(
-          fromCatchVarName.getString());
-      JsCatch catchBlock = new JsCatch(name);
+      JsCatch catchBlock = new JsCatch(getScope(), fromCatchVarName.getString());
 
       // Pre-advance to the next catch block, if any.
       // We do this here to decide whether or not this is the last one.
@@ -1113,7 +1105,9 @@
       // Map the catch body.
       //
       Node fromCatchBody = fromCondition.getNext();
+      pushScope(catchBlock.getScope());
       catchBlock.setBody(mapBlock(fromCatchBody));
+      popScope();
 
       // Attach it.
       //
@@ -1164,7 +1158,7 @@
       // literals.
       //
       String fromName = fromVar.getString();
-      JsName toName = getScope().getOrCreateObfuscatableName(fromName);
+      JsName toName = getScope().declareName(fromName);
       JsVars.JsVar toVar = new JsVars.JsVar(toName);
 
       Node fromInit = fromVar.getFirstChild();
diff --git a/dev/core/src/com/google/gwt/dev/js/JsPrettyNamer.java b/dev/core/src/com/google/gwt/dev/js/JsPrettyNamer.java
new file mode 100644
index 0000000..524712a
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/js/JsPrettyNamer.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2006 Google Inc.
+ * 
+ * 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.gwt.dev.js;
+
+import com.google.gwt.dev.js.ast.JsName;
+import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.js.ast.JsRootScope;
+import com.google.gwt.dev.js.ast.JsScope;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A namer that uses short, readable idents to maximize reability.
+ */
+public class JsPrettyNamer {
+
+  public static void exec(JsProgram program) {
+    new JsPrettyNamer(program).execImpl();
+  }
+
+  /**
+   * Communicates to a parent scope all the idents used by all child scopes.
+   */
+  private Set childIdents = null;
+
+  private final JsProgram program;
+
+  public JsPrettyNamer(JsProgram program) {
+    this.program = program;
+  }
+
+  private void execImpl() {
+    visit(program.getRootScope());
+  }
+
+  private boolean isLegal(JsScope scope, Set childIdents, String newIdent) {
+    if (JsKeywords.isKeyword(newIdent)) {
+      return false;
+    }
+    if (childIdents.contains(newIdent)) {
+      // one of my children already claimed this ident
+      return false;
+    }
+    /*
+     * Never obfuscate a name into an identifier that conflicts with an existing
+     * unobfuscatable name! It's okay if it conflicts with an existing
+     * obfuscatable name; that name will get obfuscated out of the way.
+     */
+    return (scope.findExistingUnobfuscatableName(newIdent) == null);
+  }
+
+  private void visit(JsScope scope) {
+    // Save off the childIdents which is currently being computed for my parent.
+    Set myChildIdents = childIdents;
+
+    /*
+     * Visit my children first. Reset childIdents so that my children will get a
+     * clean slate: I do not communicate to my children.
+     */
+    childIdents = new HashSet();
+    List children = scope.getChildren();
+    for (Iterator it = children.iterator(); it.hasNext();) {
+      visit((JsScope) it.next());
+    }
+
+    JsRootScope rootScope = program.getRootScope();
+    if (scope == rootScope) {
+      return;
+    }
+
+    // Visit all my idents.
+    for (Iterator it = scope.getAllNames(); it.hasNext();) {
+      JsName name = (JsName) it.next();
+      if (!name.isObfuscatable()) {
+        // Unobfuscatable names become themselves.
+        name.setShortIdent(name.getIdent());
+        continue;
+      }
+
+      String newIdent = name.getShortIdent();
+      if (!isLegal(scope, childIdents, newIdent)) {
+        String checkIdent = newIdent;
+        for (int i = 0; true; ++i) {
+          checkIdent = newIdent + "_" + i;
+          if (isLegal(scope, childIdents, checkIdent)) {
+            break;
+          }
+        }
+        name.setShortIdent(checkIdent);
+      } else {
+        // nothing to do; the short name is already good
+      }
+      childIdents.add(name.getShortIdent());
+    }
+    myChildIdents.addAll(childIdents);
+    childIdents = myChildIdents;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitor.java
index cd07f51..5587580 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitor.java
@@ -24,8 +24,8 @@
  */
 public class JsSourceGenerationVisitor extends JsToStringGenerationVisitor {
 
-  public JsSourceGenerationVisitor(TextOutput out, NamingStrategy namer) {
-    super(out, namer);
+  public JsSourceGenerationVisitor(TextOutput out) {
+    super(out);
   }
 
   // function foo(a, b) {
diff --git a/dev/core/src/com/google/gwt/dev/js/JsSymbolResolver.java b/dev/core/src/com/google/gwt/dev/js/JsSymbolResolver.java
new file mode 100644
index 0000000..e5e02c6
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/js/JsSymbolResolver.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2006 Google Inc.
+ * 
+ * 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.gwt.dev.js;
+
+import com.google.gwt.dev.js.ast.JsCatch;
+import com.google.gwt.dev.js.ast.JsFunction;
+import com.google.gwt.dev.js.ast.JsName;
+import com.google.gwt.dev.js.ast.JsNameRef;
+import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.js.ast.JsScope;
+
+import java.util.Stack;
+
+/**
+ * Resolves any unresolved JsNameRefs.
+ */
+public class JsSymbolResolver {
+
+  /**
+   * Resolves any unresolved JsNameRefs.
+   */
+  private class JsResolveSymbolsVisitor extends JsAbstractVisitorWithAllVisits {
+
+    private final Stack scopeStack = new Stack();
+
+    public void endVisit(JsCatch x) {
+      popScope();
+    }
+
+    public void endVisit(JsFunction x) {
+      popScope();
+    }
+
+    public void endVisit(JsNameRef x) {
+      if (x.isResolved()) {
+        return;
+      }
+
+      JsName name;
+      String ident = x.getIdent();
+      if (x.getQualifier() == null) {
+        name = getScope().findExistingName(ident);
+        if (name == null) {
+          // No clue what this is; create a new unobfuscatable name
+          name = program.getRootScope().declareName(ident);
+          name.setObfuscatable(false);
+        }
+      } else {
+        name = program.getObjectScope().findExistingName(ident);
+        if (name == null) {
+          // No clue what this is; create a new unobfuscatable name
+          name = program.getObjectScope().declareName(ident);
+          name.setObfuscatable(false);
+        }
+      }
+      x.resolve(name);
+    }
+
+    public boolean visit(JsCatch x) {
+      pushScope(x.getScope());
+      return true;
+    }
+
+    public boolean visit(JsFunction x) {
+      pushScope(x.getScope());
+      return true;
+    }
+
+    private JsScope getScope() {
+      return (JsScope) scopeStack.peek();
+    }
+
+    private void popScope() {
+      scopeStack.pop();
+    }
+
+    private void pushScope(JsScope scope) {
+      scopeStack.push(scope);
+    }
+  }
+
+  public static void exec(JsProgram program) {
+    new JsSymbolResolver(program).execImpl();
+  }
+
+  private final JsProgram program;
+
+  private JsSymbolResolver(JsProgram program) {
+    this.program = program;
+  }
+
+  private void execImpl() {
+    JsResolveSymbolsVisitor resolver = new JsResolveSymbolsVisitor();
+    program.traverse(resolver);
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
index b7ad13e..3e2d395 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
@@ -101,14 +101,10 @@
       'E', 'F'};
 
   protected boolean needSemi = true;
-
-  private final NamingStrategy namer;
-
   private final TextOutput p;
 
-  public JsToStringGenerationVisitor(TextOutput out, NamingStrategy namer) {
+  public JsToStringGenerationVisitor(TextOutput out) {
     this.p = out;
-    this.namer = namer;
   }
 
   public boolean visit(JsArrayAccess x) {
@@ -146,6 +142,7 @@
       _parenPopOrSpace(x, arg1, !op.isLeftAssociative());
     } else {
       _parenPop(x, arg1, !op.isLeftAssociative());
+      _spaceOpt();
     }
     p.print(op.getSymbol());
     JsExpression arg2 = x.getArg2();
@@ -153,6 +150,7 @@
     if (needSpace) {
       _parenPushOrSpace(x, arg2, op.isLeftAssociative());
     } else {
+      _spaceOpt();
       _parenPush(x, arg2, op.isLeftAssociative());
     }
     arg2.traverse(this);
@@ -215,7 +213,7 @@
     JsNameRef label = x.getLabel();
     if (label != null) {
       _space();
-      _nameOf(label);
+      _nameRef(label);
     }
 
     return false;
@@ -248,7 +246,7 @@
     _catch();
     _spaceOpt();
     _lparen();
-    _nameDef(x.getName());
+    _nameDef(x.getParameter().getName());
 
     // Optional catch condition.
     //
@@ -298,7 +296,7 @@
     JsNameRef label = x.getLabel();
     if (label != null) {
       _space();
-      _nameOf(label);
+      _nameRef(label);
     }
 
     return false;
@@ -535,7 +533,7 @@
       _parenPop(x, q, false);
       _dot();
     }
-    _nameOf(x);
+    _nameRef(x);
     return false;
   }
 
@@ -845,13 +843,15 @@
   }
 
   private void _nameDef(JsName name) {
-    String ident = namer.getIdent(name);
-    p.print(ident);
+    p.print(name.getShortIdent());
   }
 
   private void _nameOf(HasName hasName) {
-    String ident = namer.getIdent(hasName.getName());
-    p.print(ident);
+    _nameDef(hasName.getName());
+  }
+
+  private void _nameRef(JsNameRef nameRef) {
+    p.print(nameRef.getShortIdent());
   }
 
   private boolean _nestedPop(JsStatement statement) {
@@ -917,14 +917,12 @@
     return doPop;
   }
 
-  // TODO(later): we often leave a space when we don't need to
   private boolean _parenPopOrSpace(JsExpression parent, JsExpression child,
       boolean wrongAssoc) {
     boolean doPop = _parenCalc(parent, child, wrongAssoc);
     if (doPop) {
       _rparen();
     } else {
-      // if (!child.isLeaf())
       _space();
     }
     return doPop;
@@ -956,14 +954,12 @@
     return doPush;
   }
 
-  // TODO(later): we often leave a space when we don't need to
   private boolean _parenPushOrSpace(JsExpression parent, JsExpression child,
       boolean wrongAssoc) {
     boolean doPush = _parenCalc(parent, child, wrongAssoc);
     if (doPush) {
       _lparen();
     } else {
-      // if (!child.isLeaf())
       _space();
     }
     return doPush;
diff --git a/dev/core/src/com/google/gwt/dev/js/JsVerboseNamer.java b/dev/core/src/com/google/gwt/dev/js/JsVerboseNamer.java
new file mode 100644
index 0000000..9557deb
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/js/JsVerboseNamer.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2006 Google Inc.
+ * 
+ * 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.gwt.dev.js;
+
+import com.google.gwt.dev.js.ast.JsName;
+import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.js.ast.JsRootScope;
+import com.google.gwt.dev.js.ast.JsScope;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A namer that uses long, fully qualified names for maximum unambiguous
+ * debuggability.
+ */
+public class JsVerboseNamer {
+
+  public static void exec(JsProgram program) {
+    new JsVerboseNamer(program).execImpl();
+  }
+
+  private final JsProgram program;
+
+  public JsVerboseNamer(JsProgram program) {
+    this.program = program;
+  }
+
+  private void execImpl() {
+    visit(program.getRootScope());
+  }
+
+  private boolean isLegal(JsScope scope, String newIdent) {
+    // only keywords are forbidden
+    return !JsKeywords.isKeyword(newIdent);
+  }
+
+  private void visit(JsScope scope) {
+    // Visit children.
+    List children = scope.getChildren();
+    for (Iterator it = children.iterator(); it.hasNext();) {
+      visit((JsScope) it.next());
+    }
+
+    JsRootScope rootScope = program.getRootScope();
+    if (scope == rootScope) {
+      return;
+    }
+
+    // Visit all my idents.
+    for (Iterator it = scope.getAllNames(); it.hasNext();) {
+      JsName name = (JsName) it.next();
+      if (!name.isObfuscatable()) {
+        // Unobfuscatable names become themselves.
+        name.setShortIdent(name.getIdent());
+        continue;
+      }
+
+      String fullIdent = name.getIdent();
+      if (!isLegal(scope, fullIdent)) {
+        String checkIdent = fullIdent;
+        for (int i = 0; true; ++i) {
+          checkIdent = fullIdent + "_" + i;
+          if (isLegal(scope, checkIdent)) {
+            break;
+          }
+        }
+        name.setShortIdent(checkIdent);
+      } else {
+        // set each name's short ident to its full ident
+        name.setShortIdent(fullIdent);
+      }
+    }
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/js/NamingStrategy.java b/dev/core/src/com/google/gwt/dev/js/NamingStrategy.java
deleted file mode 100644
index 496f5d9..0000000
--- a/dev/core/src/com/google/gwt/dev/js/NamingStrategy.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright 2006 Google Inc.
- * 
- * 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.gwt.dev.js;
-
-import com.google.gwt.dev.js.ast.JsName;
-import com.google.gwt.dev.js.ast.JsObfuscatableName;
-import com.google.gwt.dev.js.ast.JsScope;
-import com.google.gwt.dev.js.ast.JsUnobfuscatableName;
-
-import java.util.HashMap;
-import java.util.IdentityHashMap;
-import java.util.Map;
-
-/**
- * Abstracts the process of mapping a standard identifier name onto a possibly
- * alternative name.
- */
-public abstract class NamingStrategy {
-
-  private class RootScopeHandler {
-
-    private final JsScope rootScope;
-
-    private final Map/* <JsObfuscatableName, String> */resultByName = new IdentityHashMap();
-
-    private final Map/* <JsScope, Map<String, JsName>> */nameByResultByScope = new IdentityHashMap();
-
-    public RootScopeHandler(JsScope rootScope) {
-      this.rootScope = rootScope;
-    }
-
-    public String getObfuscatedName(JsObfuscatableName name) {
-      String result = (String) resultByName.get(name);
-      if (result == null) {
-        String baseIdent = getBaseIdent(name);
-        String tailIdent = "";
-        int counter = 0;
-        JsScope scope = name.getScope();
-        do {
-          result = obfuscate(baseIdent + tailIdent, scope, rootScope);
-
-          // check for conflicts!
-          if (!conflicts(result, scope)) {
-            resultByName.put(name, result);
-            Map/* <String, JsName> */nameByResult = (Map) nameByResultByScope.get(scope);
-            if (nameByResult == null) {
-              nameByResult = new HashMap();
-              nameByResultByScope.put(scope, nameByResult);
-            }
-            nameByResult.put(result, name);
-            break;
-          }
-
-          // try this ident with a counter on it
-          tailIdent = String.valueOf(counter++);
-        } while (true);
-      }
-      return result;
-    }
-
-    private boolean childConflicts(String result, JsScope scope) {
-      for (int i = 0; i < scope.getChildren().size(); i++) {
-        JsScope child = (JsScope) scope.getChildren().get(i);
-        if (scopeConflicts(result, child)) {
-          return true;
-        }
-        if (childConflicts(result, child)) {
-          return true;
-        }
-      }
-      return false;
-    }
-
-    private boolean conflicts(String result, JsScope scope) {
-      if (JsKeywords.isKeyword(result.toCharArray())) {
-        return true;
-      }
-      if (scope.hasUnobfuscatableName(result)) {
-        return true;
-      }
-      if (scopeConflicts(result, scope)) {
-        return true;
-      }
-      if (parentConflicts(result, scope)) {
-        return true;
-      }
-      return childConflicts(result, scope);
-    }
-
-    private boolean parentConflicts(String result, JsScope scope) {
-      JsScope parent = scope.getParent();
-      if (parent == null) {
-        return false;
-      }
-      if (scopeConflicts(result, parent)) {
-        return true;
-      }
-      return parentConflicts(result, parent);
-    }
-
-    private boolean scopeConflicts(String result, JsScope scope) {
-      Map/* <String, JsName> */nameByResult = (Map) nameByResultByScope.get(scope);
-      if (nameByResult != null) {
-        return nameByResult.containsKey(result);
-      }
-      return false;
-    }
-  }
-
-  private final Map/* <JsScope, RootScopeHandler> */handlersByScope = new IdentityHashMap();
-
-  public final String getIdent(JsName name) {
-    if (name instanceof JsUnobfuscatableName) {
-      return name.getIdent();
-    }
-    RootScopeHandler handler = findHandler(name.getScope());
-    return handler.getObfuscatedName((JsObfuscatableName) name);
-  }
-
-  protected abstract String getBaseIdent(JsObfuscatableName name);
-
-  protected abstract String obfuscate(String name, JsScope scope,
-      JsScope rootScope);
-
-  private RootScopeHandler findHandler(JsScope scope) {
-    RootScopeHandler handler = (RootScopeHandler) handlersByScope.get(scope);
-    if (handler == null) {
-      JsScope parent = scope.getParent();
-      if (parent == null) {
-        handler = new RootScopeHandler(scope);
-        handlersByScope.put(scope, handler);
-      } else {
-        handler = findHandler(parent);
-      }
-    }
-    return handler;
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/js/ObfuscatedNamingStrategy.java b/dev/core/src/com/google/gwt/dev/js/ObfuscatedNamingStrategy.java
deleted file mode 100644
index 3218958..0000000
--- a/dev/core/src/com/google/gwt/dev/js/ObfuscatedNamingStrategy.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2006 Google Inc.
- * 
- * 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.gwt.dev.js;
-
-import com.google.gwt.dev.js.ast.JsObfuscatableName;
-import com.google.gwt.dev.js.ast.JsScope;
-
-/**
- * Implements a naming strategy that obfuscated the standard names of
- * identifiers.
- */
-public class ObfuscatedNamingStrategy extends NamingStrategy {
-
-  private static final char[] sBase64Chars = new char[] {
-      'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
-      'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B',
-      'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
-      'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3',
-      '4', '5', '6', '7', '8', '9', '$', '_'};
-
-  private int nextId = 0;
-
-  // @Override
-  protected String getBaseIdent(JsObfuscatableName name) {
-    return name.getIdent();
-  }
-
-  // @Override
-  protected String obfuscate(String name, JsScope scope, JsScope rootScope) {
-    String result = "";
-    int curId = nextId++;
-
-    // Use base-32 for the first character of the identifier,
-    // so that we don't use any numbers (which are illegal at
-    // the beginning of an identifier).
-    //
-    result += sBase64Chars[curId & 0x1f];
-    curId >>= 5;
-
-    // Use base-64 for the rest of the identifier.
-    //
-    while (curId != 0) {
-      result += sBase64Chars[curId & 0x3f];
-      curId >>= 6;
-    }
-
-    return result;
-  }
-
-}
diff --git a/dev/core/src/com/google/gwt/dev/js/PrettyNamingStrategy.java b/dev/core/src/com/google/gwt/dev/js/PrettyNamingStrategy.java
deleted file mode 100644
index a91485f..0000000
--- a/dev/core/src/com/google/gwt/dev/js/PrettyNamingStrategy.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2006 Google Inc.
- * 
- * 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.gwt.dev.js;
-
-import com.google.gwt.dev.js.ast.JsObfuscatableName;
-import com.google.gwt.dev.js.ast.JsScope;
-
-/**
- * Implements a naming strategy that simplifies the standard names of
- * identifiers to make them readable.
- */
-public class PrettyNamingStrategy extends NamingStrategy {
-
-  // @Override
-  protected String getBaseIdent(JsObfuscatableName name) {
-    return "_" + name.getShortIdent();
-  }
-
-  // @Override
-  protected String obfuscate(String name, JsScope scope, JsScope rootScope) {
-    return name;
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsBlock.java b/dev/core/src/com/google/gwt/dev/js/ast/JsBlock.java
index 3c721fa..47b95f0 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsBlock.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsBlock.java
@@ -15,8 +15,6 @@
  */
 package com.google.gwt.dev.js.ast;
 
-import java.util.Iterator;
-
 /**
  * Represents a JavaScript block statement.
  */
@@ -37,10 +35,7 @@
 
   public void traverse(JsVisitor v) {
     if (v.visit(this)) {
-      for (Iterator iter = stmts.iterator(); iter.hasNext();) {
-        JsStatement stmt = (JsStatement) iter.next();
-        stmt.traverse(v);
-      }
+      stmts.traverse(v);
     }
     v.endVisit(this);
   }
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsCatch.java b/dev/core/src/com/google/gwt/dev/js/ast/JsCatch.java
index c519d42..a7b93bc 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsCatch.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsCatch.java
@@ -18,16 +18,20 @@
 /**
  * Represents a JavaScript catch clause.
  */
-public class JsCatch extends JsNode implements HasCondition, HasName {
+public class JsCatch extends JsNode implements HasCondition {
+
+  protected final JsCatchScope scope;
 
   private JsBlock body;
 
   private JsExpression condition;
 
-  private final JsName name;
+  private final JsParameter param;
 
-  public JsCatch(JsName name) {
-    this.name = name;
+  public JsCatch(JsScope parent, String ident) {
+    assert (parent != null);
+    scope = new JsCatchScope(parent, ident);
+    param = new JsParameter(scope.findExistingName(ident));
   }
 
   public JsBlock getBody() {
@@ -38,8 +42,12 @@
     return condition;
   }
 
-  public JsName getName() {
-    return name;
+  public JsParameter getParameter() {
+    return param;
+  }
+
+  public JsScope getScope() {
+    return scope;
   }
 
   public void setBody(JsBlock body) {
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsCatchScope.java b/dev/core/src/com/google/gwt/dev/js/ast/JsCatchScope.java
new file mode 100644
index 0000000..2466524
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsCatchScope.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2006 Google Inc.
+ * 
+ * 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.gwt.dev.js.ast;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * A special scope used only for catch blocks. It only holds a single symbol:
+ * the catch argument's name.
+ */
+public class JsCatchScope extends JsScope {
+
+  private final JsName name;
+
+  public JsCatchScope(JsScope parent, String ident) {
+    super(parent, "Catch scope");
+    this.name = new JsName(ident, ident);
+  }
+
+  public JsName declareName(String ident) {
+    // Declare into parent scope!
+    return getParent().declareName(ident);
+  }
+
+  public JsName declareName(String ident, String shortIdent) {
+    // Declare into parent scope!
+    return getParent().declareName(ident, shortIdent);
+  }
+
+  public Iterator getAllNames() {
+    return new Iterator() {
+      private boolean didIterate = false;
+
+      public boolean hasNext() {
+        return !didIterate;
+      }
+
+      public Object next() {
+        if (didIterate) {
+          throw new NoSuchElementException();
+        }
+        didIterate = true;
+        return name;
+      }
+
+      public void remove() {
+        throw new UnsupportedOperationException();
+      }
+
+    };
+  }
+
+  protected JsName doCreateName(String ident, String shortIdent) {
+    throw new UnsupportedOperationException(
+        "Cannot create a name in a catch scope");
+  }
+
+  protected JsName findExistingNameNoRecurse(String ident) {
+    if (name.getIdent().equals(ident)) {
+      return name;
+    } else {
+      return null;
+    }
+  }
+
+}
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsFunction.java b/dev/core/src/com/google/gwt/dev/js/ast/JsFunction.java
index 9e2b68b..83f8e03 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsFunction.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsFunction.java
@@ -18,16 +18,13 @@
 /**
  * Represents a JavaScript function expression.
  */
-public final class JsFunction extends JsExpression implements HasName, HasScope {
+public class JsFunction extends JsExpression implements HasName {
 
-  private JsBlock body;
-
+  protected JsBlock body;
+  protected final JsParameters params = new JsParameters();
+  protected final JsScope scope;
   private JsName name;
 
-  private final JsParameters params = new JsParameters();
-
-  private final JsScope scope;
-
   /**
    * Creates an anonymous function.
    */
@@ -40,12 +37,10 @@
    */
   public JsFunction(JsScope parent, JsName name) {
     assert (parent != null);
-    this.scope = new JsScope(parent);
-    setName(name);
-  }
-
-  public void accept(JsVisitor v) {
-    v.visit(this);
+    this.name = name;
+    String scopeName = (name == null) ? "<anonymous>" : name.getIdent();
+    scopeName = "function " + scopeName;
+    this.scope = new JsScope(parent, scopeName);
   }
 
   public JsBlock getBody() {
@@ -70,11 +65,6 @@
 
   public void setName(JsName name) {
     this.name = name;
-    String desc = "function <anonymous>";
-    if (name != null) {
-      desc = "function " + name.getIdent();
-    }
-    scope.setDescription(desc);
   }
 
   public void traverse(JsVisitor v) {
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsName.java b/dev/core/src/com/google/gwt/dev/js/ast/JsName.java
index c60bdff..5d9fbcc 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsName.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsName.java
@@ -18,36 +18,48 @@
 /**
  * An abstract base class for named JavaScript objects.
  */
-public abstract class JsName {
+public class JsName {
 
   private final String ident;
-
-  private final JsScope scope;
+  private boolean isObfuscatable;
+  private String shortIdent;
 
   /**
    * @param scope the scope in which this name is defined
    * @param ident the unmangled ident to use for this name
    */
-  protected JsName(JsScope scope, String ident) {
-    this.scope = scope;
+  JsName(String ident, String shortIdent) {
     this.ident = ident;
+    this.shortIdent = shortIdent;
+    this.isObfuscatable = true;
   }
 
   public String getIdent() {
     return ident;
   }
 
-  public JsScope getScope() {
-    return scope;
+  public String getShortIdent() {
+    return shortIdent;
   }
 
-  public abstract boolean isObfuscatable();
+  public boolean isObfuscatable() {
+    return isObfuscatable;
+  }
 
   public JsNameRef makeRef() {
     return new JsNameRef(this);
   }
 
+  public void setObfuscatable(boolean isObfuscatable) {
+    this.isObfuscatable = isObfuscatable;
+  }
+
+  public void setShortIdent(String shortIdent) {
+    this.shortIdent = shortIdent;
+  }
+
   public String toString() {
     return ident;
   }
+
 }
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsNameRef.java b/dev/core/src/com/google/gwt/dev/js/ast/JsNameRef.java
index 09c434a..c04931e 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsNameRef.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsNameRef.java
@@ -18,24 +18,32 @@
 /**
  * Represents a JavaScript expression that references a name.
  */
-public final class JsNameRef extends JsExpression implements HasName {
+public final class JsNameRef extends JsExpression /*implements HasName*/ {
 
+  private String ident;
   private JsName name;
-
   private JsExpression qualifier;
 
   public JsNameRef(JsName name) {
     this.name = name;
   }
 
-  public JsName getName() {
-    return name;
+  public JsNameRef(String ident) {
+    this.ident = ident;
+  }
+
+  public String getIdent() {
+    return (name == null) ? ident : name.getIdent();
   }
 
   public JsExpression getQualifier() {
     return qualifier;
   }
 
+  public String getShortIdent() {
+    return (name == null) ? ident : name.getShortIdent();
+  }
+
   public boolean isLeaf() {
     if (qualifier == null) {
       return true;
@@ -44,8 +52,13 @@
     }
   }
 
-  public void setName(JsName name) {
+  public boolean isResolved() {
+    return name != null;
+  }
+
+  public void resolve(JsName name) {
     this.name = name;
+    this.ident = null;
   }
 
   public void setQualifier(JsExpression qualifier) {
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsNode.java b/dev/core/src/com/google/gwt/dev/js/ast/JsNode.java
index 49580f9..dc11285 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsNode.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsNode.java
@@ -15,9 +15,7 @@
  */
 package com.google.gwt.dev.js.ast;
 
-import com.google.gwt.dev.js.FullNamingStrategy;
 import com.google.gwt.dev.js.JsToStringGenerationVisitor;
-import com.google.gwt.dev.js.NamingStrategy;
 import com.google.gwt.dev.util.TextOutputOnCharArray;
 
 /**
@@ -32,10 +30,8 @@
   
   // @Override
   public String toString() {
-    // no obfuscation
-    NamingStrategy ns = new FullNamingStrategy();
     TextOutputOnCharArray p = new TextOutputOnCharArray(false);
-    JsToStringGenerationVisitor v = new JsToStringGenerationVisitor(p, ns);
+    JsToStringGenerationVisitor v = new JsToStringGenerationVisitor(p);
     traverse(v);
     return new String(p.getText());
   }
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsObfuscatableName.java b/dev/core/src/com/google/gwt/dev/js/ast/JsObfuscatableName.java
deleted file mode 100644
index 0aed20b..0000000
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsObfuscatableName.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2006 Google Inc.
- * 
- * 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.gwt.dev.js.ast;
-
-/**
- * An obfuscatable JavaScript name.
- */
-public final class JsObfuscatableName extends JsName {
-
-  private final String shortIdent;
-
-  /**
-   * @param scope the scope in which this name is defined
-   * @param ident the unmangled ident to use for this name
-   */
-  protected JsObfuscatableName(JsScope scope, String ident, String shortIdent) {
-    super(scope, ident);
-    this.shortIdent = shortIdent;
-  }
-
-  public String getShortIdent() {
-    return shortIdent;
-  }
-  
-  public boolean isObfuscatable() {
-    return true;
-  }
-
-}
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java b/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java
index 82cd16b..c3807da 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsProgram.java
@@ -22,7 +22,9 @@
 /**
  * A JavaScript program.
  */
-public final class JsProgram extends JsNode implements HasScope {
+public final class JsProgram extends JsNode {
+
+  private JsStatement debuggerStmt;
 
   private final Map decimalLiteralMap = new HashMap();
 
@@ -36,22 +38,25 @@
 
   private final JsNullLiteral nullLiteral = new JsNullLiteral();
 
+  private final JsScope objectScope;
+
   private final JsRootScope rootScope;
 
   private final Map stringLiteralMap = new HashMap();
 
-  private final JsBooleanLiteral trueLiteral = new JsBooleanLiteral(true);
+  private final JsScope topScope;
 
-  private JsStatement debuggerStmt;
+  private final JsBooleanLiteral trueLiteral = new JsBooleanLiteral(true);
 
   /**
    * Constructs a JavaScript program object.
    */
   public JsProgram() {
     rootScope = new JsRootScope(this);
-    rootScope.setDescription("Global");
     globalBlock = new JsGlobalBlock();
-    JsName debugger = rootScope.createSpecialUnobfuscatableName("debugger");
+    topScope = new JsScope(rootScope, "Global");
+    objectScope = new JsScope(rootScope, "Object");
+    JsName debugger = rootScope.findExistingName("debugger");
     debuggerStmt = new JsExprStmt(debugger.makeRef());
   }
 
@@ -102,11 +107,25 @@
     return nullLiteral;
   }
 
+  public JsScope getObjectScope() {
+    return objectScope;
+  }
+
   /**
-   * Gets the one and only root scope.
+   * Gets the quasi-mythical root scope. This is not the same as the top scope;
+   * all unresolvable identifiers wind up here, because they are considered
+   * external to the program.
+   */
+  public JsRootScope getRootScope() {
+    return rootScope;
+  }
+
+  /**
+   * Gets the top level scope. This is the scope of all the statements in the
+   * main program.
    */
   public JsScope getScope() {
-    return rootScope;
+    return topScope;
   }
 
   public JsStringLiteral getStringLiteral(String value) {
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsRootScope.java b/dev/core/src/com/google/gwt/dev/js/ast/JsRootScope.java
index 25a6caf..e173a46 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsRootScope.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsRootScope.java
@@ -15,84 +15,42 @@
  */
 package com.google.gwt.dev.js.ast;
 
-import com.google.gwt.dev.js.JsKeywords;
-
-import java.util.HashMap;
-import java.util.Map;
-
 /**
- * The root scope is the parent of every scope. It is used to enforce
- * blacklisting of keywords at every scope.
+ * The root scope is the parent of every scope. All identifiers in this scope
+ * are not obfuscatable.
  */
 public final class JsRootScope extends JsScope {
 
   private final JsProgram program;
 
-  private final Map/* <String, JsUnobfuscatableName> */unobfuscatableNames = new HashMap();
-
   public JsRootScope(JsProgram program) {
+    super("Root");
     this.program = program;
     ctorAddKnownGlobalSymbols();
   }
 
-  // @Override
-  public JsName findExistingName(String ident) {
-    JsName name = (JsName) unobfuscatableNames.get(ident);
-    if (name != null) {
-      return name;
-    }
-    return super.findExistingName(ident);
-  }
-
-  // @Override
-  public JsUnobfuscatableName getOrCreateUnobfuscatableName(String ident) {
-    if (JsKeywords.isKeyword(ident.toCharArray())) {
-      throw new IllegalArgumentException("Cannot create identifier " + ident
-          + "; that name is a reserved word.");
-    }
-    JsUnobfuscatableName name = (JsUnobfuscatableName) unobfuscatableNames.get(ident);
-    if (name == null) {
-      name = new JsUnobfuscatableName(this, ident);
-      unobfuscatableNames.put(ident, name);
-    }
-    return name;
-  }
-
-  // @Override
   public JsProgram getProgram() {
     return program;
   }
 
-  // @Override
-  public boolean hasUnobfuscatableName(String result) {
-    return unobfuscatableNames.containsKey(result);
-  }
-
-  // @Override
-  public String toString() {
-    return getDescription();
-  }
-
-  JsName createSpecialUnobfuscatableName(String ident) {
-    // this is for the debugger statement; ignore keyword restriction
-    JsUnobfuscatableName name = (JsUnobfuscatableName) unobfuscatableNames.get(ident);
-    if (name == null) {
-      name = new JsUnobfuscatableName(this, ident);
-      unobfuscatableNames.put(ident, name);
-    }
+  protected JsName doCreateName(String ident, String shortIdent) {
+    JsName name = super.doCreateName(ident, shortIdent);
+    name.setObfuscatable(false);
     return name;
   }
 
   private void ctorAddKnownGlobalSymbols() {
+    // HACK: debugger is modelled as an ident even though it's really a keyword
     String[] commonBuiltins = new String[] {
         "ActiveXObject", "Array", "Boolean", "Date", "Debug", "Enumerator",
         "Error", "Function", "Global", "Image", "Math", "Number", "Object",
         "RegExp", "String", "VBArray", "window", "document", "event",
-        "arguments", "call", "toString", "$wnd", "$doc", "$moduleName"};
+        "arguments", "call", "toString", "$wnd", "$doc", "$moduleName",
+        "debugger", "undefined"};
 
     for (int i = 0; i < commonBuiltins.length; i++) {
       String ident = commonBuiltins[i];
-      this.getOrCreateUnobfuscatableName(ident);
+      this.doCreateName(ident, ident);
     }
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsScope.java b/dev/core/src/com/google/gwt/dev/js/ast/JsScope.java
index 50e24cb..3dc84e7 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsScope.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsScope.java
@@ -15,10 +15,13 @@
  */
 package com.google.gwt.dev.js.ast;
 
+import com.google.gwt.dev.js.JsKeywords;
+
 import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.TreeMap;
 
 /**
  * A scope is a factory for creating and allocating
@@ -48,18 +51,16 @@
 public class JsScope {
 
   private final List/* <JsScope> */children = new ArrayList();
-
-  private String description;
-
-  private final Map/* <String, JsObfuscatableName> */obfuscatableNames = new HashMap();
-
+  private final String description;
+  private final Map/* <String, JsName> */names = new TreeMap();
   private final JsScope parent;
 
   /**
    * Create a scope with parent.
    */
-  public JsScope(JsScope parent) {
+  public JsScope(JsScope parent, String description) {
     assert (parent != null);
+    this.description = description;
     this.parent = parent;
     this.parent.children.add(this);
   }
@@ -67,38 +68,53 @@
   /**
    * Subclasses can be parentless.
    */
-  protected JsScope() {
+  protected JsScope(String description) {
+    this.description = description;
     this.parent = null;
   }
 
   /**
-   * Creates an obfuscatable name object associated with the specified ident in
-   * this scope.
+   * Gets a name object associated with the specified ident in this scope,
+   * creating it if necessary.
    * 
-   * @param ident An identifier that must be unique within this scope.
-   * @throws IllegalArgumentException if ident already exists in this scope.
+   * @param ident An identifier that is unique within this scope.
    */
-  public JsObfuscatableName createUniqueObfuscatableName(String ident) {
-    if (obfuscatableNames.containsKey(ident)) {
-      throw new IllegalArgumentException("Identifier already in use: " + ident);
+  public JsName declareName(String ident) {
+    JsName name = findExistingNameNoRecurse(ident);
+    if (name != null) {
+      return name;
     }
-    return getOrCreateObfuscatableName(ident, ident);
+    if (JsKeywords.isKeyword(ident)) {
+      throw new IllegalArgumentException("Cannot create identifier " + ident
+          + "; that name is a reserved word.");
+    }
+    return doCreateName(ident, ident);
   }
 
   /**
-   * Creates an obfuscatable name object associated with the specified ident in
-   * this scope.
+   * Gets a name object associated with the specified ident in this scope,
+   * creating it if necessary.
    * 
-   * @param ident An identifier that must be unique within this scope.
+   * @param ident An identifier that is unique within this scope.
    * @param shortIdent A "pretty" name that does not have to be unique.
-   * @throws IllegalArgumentException if ident already exists in this scope.
+   * @throws IllegalArgumentException if ident already exists in this scope but
+   *           the requested short name does not match the existing short name.
    */
-  public JsObfuscatableName createUniqueObfuscatableName(String ident,
-      String shortIdent) {
-    if (obfuscatableNames.containsKey(ident)) {
-      throw new IllegalArgumentException("Identifier already in use: " + ident);
+  public JsName declareName(String ident, String shortIdent) {
+    JsName name = findExistingNameNoRecurse(ident);
+    if (name != null) {
+      if (!name.getShortIdent().equals(shortIdent)) {
+        throw new IllegalArgumentException("Requested short name " + shortIdent
+            + " conflicts with preexisting short name " + name.getShortIdent()
+            + " for identifier " + ident);
+      }
+      return name;
     }
-    return getOrCreateObfuscatableName(ident, shortIdent);
+    if (JsKeywords.isKeyword(ident)) {
+      throw new IllegalArgumentException("Cannot create identifier " + ident
+          + "; that name is a reserved word.");
+    }
+    return doCreateName(ident, shortIdent);
   }
 
   /**
@@ -107,100 +123,86 @@
    * 
    * @return <code>null</code> if the identifier has no associated name
    */
-  public JsName findExistingName(String ident) {
-    JsName name = (JsName) obfuscatableNames.get(ident);
-    if (name != null) {
-      return name;
-    }
-
-    if (parent != null) {
+  public final JsName findExistingName(String ident) {
+    JsName name = findExistingNameNoRecurse(ident);
+    if (name == null && parent != null) {
       return parent.findExistingName(ident);
     }
-
-    return null;
+    return name;
   }
 
-  public List/* <JsScope> */getChildren() {
+  /**
+   * Attempts to find an unobfuscatable name object for the specified ident,
+   * searching in this scope, and if not found, in the parent scopes.
+   * 
+   * @return <code>null</code> if the identifier has no associated name
+   */
+  public final JsName findExistingUnobfuscatableName(String ident) {
+    JsName name = findExistingNameNoRecurse(ident);
+    if (name != null && name.isObfuscatable()) {
+      name = null;
+    }
+    if (name == null && parent != null) {
+      return parent.findExistingUnobfuscatableName(ident);
+    }
+    return name;
+  }
+
+  /**
+   * Returns an iterator for all the names defined by this scope.
+   */
+  public Iterator getAllNames() {
+    return names.values().iterator();
+  }
+
+  /**
+   * Returns a list of this scope's child scopes.
+   */
+  public final List getChildren() {
     return children;
   }
 
   /**
-   * Gets an obfuscatable name object associated with the specified ident in
-   * this scope, creating it if necessary.
-   * 
-   * @param ident An identifier that is unique within this scope.
+   * Returns the parent scope of this scope, or <code>null</code> if this is the
+   * root scope.
    */
-  public JsObfuscatableName getOrCreateObfuscatableName(String ident) {
-    JsObfuscatableName name = (JsObfuscatableName) obfuscatableNames.get(ident);
-    if (name == null) {
-      name = new JsObfuscatableName(this, ident, ident);
-      obfuscatableNames.put(ident, name);
-    }
-    return name;
-  }
-
-  /**
-   * Gets an obfuscatable name object associated with the specified ident in
-   * this scope, creating it if necessary.
-   * 
-   * @param ident An identifier that is unique within this scope.
-   * @param shortIdent A "pretty" name that does not have to be unique.
-   * @throws IllegalArgumentException if ident already exists in this scope but
-   *           the requested short name does not match the existing short name.
-   */
-  public JsObfuscatableName getOrCreateObfuscatableName(String ident,
-      String shortIdent) {
-    JsObfuscatableName name = (JsObfuscatableName) obfuscatableNames.get(ident);
-    if (name == null) {
-      name = new JsObfuscatableName(this, ident, shortIdent);
-      obfuscatableNames.put(ident, name);
-    } else {
-      if (!name.getShortIdent().equals(shortIdent)) {
-        throw new IllegalArgumentException("Requested short name " + shortIdent
-            + " conflicts with preexisting short name " + name.getShortIdent()
-            + " for identifier " + ident);
-      }
-    }
-    return name;
-  }
-
-  /**
-   * Gets an unobfuscatable name object associated with the specified ident in
-   * this scope, creating it if necessary.
-   * 
-   * @param ident An identifier that is unique within this scope.
-   * @throws IllegalArgumentException if ident is a reserved word.
-   */
-  public JsUnobfuscatableName getOrCreateUnobfuscatableName(String ident) {
-    assert (parent != null) : "Subclasses must override getOrCreateUnobfuscatableName() if they do not set a parent";
-    return parent.getOrCreateUnobfuscatableName(ident);
-  }
-
-  public JsScope getParent() {
+  public final JsScope getParent() {
     return parent;
   }
 
+  /**
+   * Returns the associated program.
+   */
   public JsProgram getProgram() {
     assert (parent != null) : "Subclasses must override getProgram() if they do not set a parent";
     return parent.getProgram();
   }
 
-  public boolean hasUnobfuscatableName(String ident) {
-    assert (parent != null) : "Subclasses must override hasUnobfuscatableName() if they do not set a parent";
-    return parent.hasUnobfuscatableName(ident);
+  public final String toString() {
+    if (parent != null) {
+      return description + "->" + parent;
+    } else {
+      return description;
+    }
   }
 
-  public void setDescription(String desc) {
-    this.description = desc;
+  /**
+   * Creates a new name in this scope.
+   */
+  protected JsName doCreateName(String ident, String shortIdent) {
+    JsName name = new JsName(ident, shortIdent);
+    names.put(ident, name);
+    return name;
   }
 
-  public String toString() {
-    assert (parent != null) : "Subclasses must override toString() if they do not set a parent";
-    return description + "->" + parent;
-  }
-
-  protected String getDescription() {
-    return description;
+  /**
+   * Attempts to find the name object for the specified ident, searching in this
+   * scope only.
+   * 
+   * @return <code>null</code> if the identifier has no associated name
+   */
+  protected JsName findExistingNameNoRecurse(String ident) {
+    return (JsName) names.get(ident);
   }
 
 }
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsUnobfuscatableName.java b/dev/core/src/com/google/gwt/dev/js/ast/JsUnobfuscatableName.java
deleted file mode 100644
index 4e2f107..0000000
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsUnobfuscatableName.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2006 Google Inc.
- * 
- * 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.gwt.dev.js.ast;
-
-/**
- * A JavaScript unobfuscatable name.
- */
-public final class JsUnobfuscatableName extends JsName {
-
-  /**
-   * @param scope the scope in which this name is defined
-   * @param ident the unmangled ident to use for this name
-   */
-  protected JsUnobfuscatableName(JsScope scope, String ident) {
-    super(scope, ident);
-  }
-
-  public boolean isObfuscatable() {
-    return false;
-  }
-
-}
diff --git a/dev/core/src/com/google/gwt/dev/util/Jsni.java b/dev/core/src/com/google/gwt/dev/util/Jsni.java
index 9c89fa1..c544314 100644
--- a/dev/core/src/com/google/gwt/dev/util/Jsni.java
+++ b/dev/core/src/com/google/gwt/dev/util/Jsni.java
@@ -20,17 +20,14 @@
 import com.google.gwt.core.ext.typeinfo.JMethod;
 import com.google.gwt.core.ext.typeinfo.JParameter;
 import com.google.gwt.core.ext.typeinfo.JType;
-import com.google.gwt.dev.js.FullNamingStrategy;
 import com.google.gwt.dev.js.JsParser;
 import com.google.gwt.dev.js.JsParserException;
 import com.google.gwt.dev.js.JsSourceGenerationVisitor;
-import com.google.gwt.dev.js.NamingStrategy;
 import com.google.gwt.dev.js.JsParserException.SourceDetail;
 import com.google.gwt.dev.js.ast.JsBlock;
 import com.google.gwt.dev.js.ast.JsExprStmt;
 import com.google.gwt.dev.js.ast.JsExpression;
 import com.google.gwt.dev.js.ast.JsFunction;
-import com.google.gwt.dev.js.ast.JsName;
 import com.google.gwt.dev.js.ast.JsNameRef;
 import com.google.gwt.dev.js.ast.JsProgram;
 import com.google.gwt.dev.js.ast.JsStatements;
@@ -63,14 +60,13 @@
   private static class VisitorImpl extends JsSourceGenerationVisitor {
     private final TextOutput out;
 
-    public VisitorImpl(TextOutput out, NamingStrategy namer) {
-      super(out, namer);
+    public VisitorImpl(TextOutput out) {
+      super(out);
       this.out = out;
     }
 
     public boolean visit(JsNameRef x) {
-      JsName name = x.getName();
-      String ident = name.getIdent();
+      String ident = x.getIdent();
       if (ident.startsWith("@")) {
         // Fix up JSNI references in the js body.
         // Cases:
@@ -259,9 +255,8 @@
   }
 
   public static String generateJavaScript(JsVisitable node) {
-    NamingStrategy namer = new FullNamingStrategy();
     TextOutputOnCharArray tooca = new TextOutputOnCharArray(false);
-    VisitorImpl vi = new VisitorImpl(tooca, namer);
+    VisitorImpl vi = new VisitorImpl(tooca);
     node.traverse(vi);
     char[] source = tooca.getText();
     return String.valueOf(source);