blob: 6fe40f792b9e3b3eb2ba2a652cbf95722ca51d4e [file] [log] [blame]
/*
* Copyright 2007 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.JsScope;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* A namer that uses short, readable idents to maximize reability.
*/
public class JsPrettyNamer extends JsNamer {
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<String> childIdents = null;
public JsPrettyNamer(JsProgram program) {
super(program);
}
@Override
protected void reset() {
childIdents = new HashSet<String>();
}
@Override
protected void visit(JsScope scope) {
// Save off the childIdents which is currently being computed for my parent.
Set<String> 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<String>();
for (JsScope child : scope.getChildren()) {
visit(child);
}
// Child idents now contains all idents my children are using.
// The next integer to try as an identifier suffix.
HashMap<String, Integer> startIdent = new HashMap<String, Integer>();
// Visit all my idents.
for (Iterator<JsName> it = scope.getAllNames(); it.hasNext();) {
JsName name = it.next();
if (!referenced.contains(name)) {
// Don't allocate idents for non-referenced names.
continue;
}
if (!name.isObfuscatable()) {
// Unobfuscatable names become themselves.
name.setShortIdent(name.getIdent());
continue;
}
String newIdent = name.getShortIdent();
if (!isLegal(scope, childIdents, newIdent)) {
String checkIdent;
// Start searching using a suffix hint stored in the scope.
// We still do a search in case there is a collision with
// a user-provided identifier
Integer s = startIdent.get(newIdent);
int suffix = (s == null) ? 0 : s.intValue();
do {
checkIdent = newIdent + "_" + suffix++;
} while (!isLegal(scope, childIdents, checkIdent));
startIdent.put(newIdent, suffix);
name.setShortIdent(checkIdent);
} else {
// nothing to do; the short name is already good
}
childIdents.add(name.getShortIdent());
}
myChildIdents.addAll(childIdents);
childIdents = myChildIdents;
}
private boolean isLegal(JsScope scope, Set<String> 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);
}
}