blob: a6a0efceee0eaba109eb9e163dc7d9be4be294f0 [file] [log] [blame]
/*
* Copyright 2014 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.cfg.ConfigProps;
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 com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
import com.google.gwt.thirdparty.guava.common.collect.HashMultiset;
import com.google.gwt.thirdparty.guava.common.collect.Maps;
import com.google.gwt.thirdparty.guava.common.collect.Multiset;
import com.google.gwt.thirdparty.guava.common.collect.Sets;
import java.util.Map;
import java.util.Set;
/**
* A namer that keeps the short ("pretty") identifier wherever possible. The original ident ->
* pretty ident mappings are recorded and can be persisted across multiple compiles.
*/
public class JsPersistentPrettyNamer extends JsNamer {
/**
* Encapsulates the complete state of this namer so that state can be persisted and reused.
*/
public static class PersistentPrettyNamerState {
private Multiset<String> shortIdentCollisionCounts = HashMultiset.create();
private Map<String, String> prettyIdentByOriginalIdent = Maps.newHashMap();
private Set<String> usedPrettyIdents = Sets.newHashSet();
}
@VisibleForTesting
public static final String RESERVED_IDENT_SUFFIX = "_g$";
public static void exec(JsProgram program, ConfigProps config, PersistentPrettyNamerState state)
throws IllegalNameException {
new JsPersistentPrettyNamer(program, config, state).execImpl();
}
private final PersistentPrettyNamerState state;
public JsPersistentPrettyNamer(JsProgram program, ConfigProps config,
PersistentPrettyNamerState state) {
super(program, config);
this.state = state;
}
@Override
protected void reset() {
// Nothing to do.
}
@Override
protected void visit(JsScope scope) throws IllegalNameException {
// Visit children.
for (JsScope child : scope.getChildren()) {
visit(child);
}
// Visit all my idents.
for (JsName name : scope.getAllNames()) {
if (!name.isObfuscatable()) {
// Unobfuscatable names become themselves.
String prettyIdent = name.getIdent();
if (prettyIdent.endsWith(RESERVED_IDENT_SUFFIX)) {
throw new IllegalNameException("Identifier " + prettyIdent + " ends with "
+ RESERVED_IDENT_SUFFIX
+ ". This is not allowed since that suffix is used to separate obfuscatable and "
+ "nonobfuscatable names in per-file compiles.");
}
name.setShortIdent(prettyIdent);
continue;
}
name.setShortIdent(getOrCreatePrettyIdent(name));
}
}
private String getOrCreatePrettyIdent(JsName name) {
String originalIdent = name.getIdent();
String shortIdent = name.getShortIdent();
// Reuse previous names.
if (state.prettyIdentByOriginalIdent.containsKey(originalIdent)) {
return state.prettyIdentByOriginalIdent.get(originalIdent);
}
// Otherwise come up with a new pretty name and cache it for reuse.
String prettyIdent = makePrettyName(shortIdent);
state.usedPrettyIdents.add(prettyIdent);
state.prettyIdentByOriginalIdent.put(originalIdent, prettyIdent);
return prettyIdent;
}
private String makePrettyName(String shortIdent) {
while (true) {
String prettyIdent = shortIdent + "_" + state.shortIdentCollisionCounts.count(shortIdent)
+ RESERVED_IDENT_SUFFIX;
state.shortIdentCollisionCounts.add(shortIdent);
if (reserved.isAvailable(prettyIdent) && !state.usedPrettyIdents.contains(prettyIdent)) {
return prettyIdent;
}
}
}
}