com.google.javascript.jscomp.ReplaceIdGenerators Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of closure-compiler Show documentation
Show all versions of closure-compiler Show documentation
Closure Compiler is a JavaScript optimizing compiler. It parses your
JavaScript, analyzes it, removes dead code and rewrites and minimizes
what's left. It also checks syntax, variable references, and types, and
warns about common JavaScript pitfalls. It is used in many of Google's
JavaScript apps, including Gmail, Google Web Search, Google Maps, and
Google Docs.
/*
* Copyright 2009 The Closure Compiler Authors.
*
* 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.javascript.jscomp;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.primitives.Booleans;
import com.google.debugging.sourcemap.Base64;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
/**
* Replaces calls to id generators with ids.
*
* Use this to get unique and short ids.
*
*/
class ReplaceIdGenerators implements CompilerPass {
static final DiagnosticType NON_GLOBAL_ID_GENERATOR_CALL =
DiagnosticType.error(
"JSC_NON_GLOBAL_ID_GENERATOR_CALL",
"Id generator call must be in the global scope");
static final DiagnosticType CONDITIONAL_ID_GENERATOR_CALL =
DiagnosticType.error(
"JSC_CONDITIONAL_ID_GENERATOR_CALL",
"Id generator call must be unconditional");
static final DiagnosticType CONFLICTING_GENERATOR_TYPE =
DiagnosticType.error(
"JSC_CONFLICTING_ID_GENERATOR_TYPE",
"Id generator can only be one of " +
"consistent, inconsistent, mapped, stable, or xid.");
static final DiagnosticType INVALID_GENERATOR_ID_MAPPING =
DiagnosticType.error(
"JSC_INVALID_GENERATOR_ID_MAPPING",
"Invalid generator id mapping. {0}");
static final DiagnosticType MISSING_NAME_MAP_FOR_GENERATOR =
DiagnosticType.warning(
"JSC_MISSING_NAME_MAP_FOR_GENERATOR",
"The mapped id generator, does not have a renaming map supplied.");
static final DiagnosticType INVALID_GENERATOR_PARAMETER =
DiagnosticType.warning(
"JSC_INVALID_GENERATOR_PARAMETER",
"An id generator must be called with a literal.");
static final DiagnosticType SHORTHAND_FUNCTION_NOT_SUPPORTED_IN_ID_GEN =
DiagnosticType.error(
"JSC_SHORTHAND_FUNCTION_NOT_SUPPORTED_IN_ID_GEN",
"Object literal shorthand functions is not allowed in the "
+ "arguments of an id generator");
static final DiagnosticType COMPUTED_PROP_NOT_SUPPORTED_IN_ID_GEN =
DiagnosticType.error(
"JSC_COMPUTED_PROP_NOT_SUPPORTED_IN_ID_GEN",
"Object literal computed property name is not allowed in the "
+ "arguments of an id generator");
private final AbstractCompiler compiler;
private final Map nameGenerators;
private final Map> consistNameMap;
private final Map> idGeneratorMaps;
private final Map> previousMap;
private final boolean generatePseudoNames;
private final Xid.HashFunction xidHashFunction;
public ReplaceIdGenerators(
AbstractCompiler compiler, Map idGens,
boolean generatePseudoNames,
String previousMapSerialized,
Xid.HashFunction xidHashFunction) {
this.compiler = compiler;
this.generatePseudoNames = generatePseudoNames;
this.xidHashFunction = xidHashFunction;
nameGenerators = new LinkedHashMap<>();
idGeneratorMaps = new LinkedHashMap<>();
consistNameMap = new LinkedHashMap<>();
Map> previousMap;
previousMap = IdMappingUtil.parseSerializedIdMappings(previousMapSerialized);
this.previousMap = previousMap;
if (idGens != null) {
for (Entry gen : idGens.entrySet()) {
String name = gen.getKey();
RenamingMap map = gen.getValue();
if (map instanceof UniqueRenamingToken) {
nameGenerators.put(name,
createNameSupplier(
RenameStrategy.INCONSISTENT, previousMap.get(name)));
} else {
nameGenerators.put(name,
createNameSupplier(
RenameStrategy.MAPPED, map));
}
idGeneratorMaps.put(name, new LinkedHashMap());
}
}
}
enum RenameStrategy {
CONSISTENT,
INCONSISTENT,
MAPPED,
STABLE,
XID
}
private static interface NameSupplier {
String getName(String id, String name);
RenameStrategy getRenameStrategy();
}
private static class ObfuscatedNameSupplier implements NameSupplier {
private final NameGenerator generator;
private final Map previousMappings;
private final RenameStrategy renameStrategy;
public ObfuscatedNameSupplier(
RenameStrategy renameStrategy, BiMap previousMappings) {
this.previousMappings = previousMappings.inverse();
this.generator =
new DefaultNameGenerator(previousMappings.keySet(), "", null);
this.renameStrategy = renameStrategy;
}
@Override
public String getName(String id, String name) {
String newName = previousMappings.get(id);
if (newName == null) {
newName = generator.generateNextName();
}
return newName;
}
@Override
public RenameStrategy getRenameStrategy() {
return renameStrategy;
}
}
private static class PseudoNameSupplier implements NameSupplier {
private int counter = 0;
private final RenameStrategy renameStrategy;
public PseudoNameSupplier(RenameStrategy renameStrategy) {
this.renameStrategy = renameStrategy;
}
@Override
public String getName(String id, String name) {
if (renameStrategy == RenameStrategy.INCONSISTENT) {
return name + "$" + counter++;
}
return name + "$0";
}
@Override
public RenameStrategy getRenameStrategy() {
return renameStrategy;
}
}
private static class StableNameSupplier implements NameSupplier {
@Override
public String getName(String id, String name) {
return Base64.base64EncodeInt(name.hashCode());
}
@Override
public RenameStrategy getRenameStrategy() {
return RenameStrategy.STABLE;
}
}
private static class XidNameSupplier implements NameSupplier {
final Xid xid;
XidNameSupplier(Xid.HashFunction hashFunction) {
this.xid = hashFunction == null ? new Xid() : new Xid(hashFunction);
}
@Override
public String getName(String id, String name) {
return xid.get(name);
}
@Override
public RenameStrategy getRenameStrategy() {
return RenameStrategy.XID;
}
}
private static class MappedNameSupplier implements NameSupplier {
private final RenamingMap map;
MappedNameSupplier(RenamingMap map) {
this.map = map;
}
@Override
public String getName(String id, String name) {
return map.get(name);
}
@Override
public RenameStrategy getRenameStrategy() {
return RenameStrategy.MAPPED;
}
}
private NameSupplier createNameSupplier(
RenameStrategy renameStrategy, BiMap previousMappings) {
previousMappings = previousMappings != null ?
previousMappings :
ImmutableBiMap.of();
if (renameStrategy == RenameStrategy.STABLE) {
return new StableNameSupplier();
} else if (renameStrategy == RenameStrategy.XID) {
return new XidNameSupplier(this.xidHashFunction);
} else if (generatePseudoNames) {
return new PseudoNameSupplier(renameStrategy);
} else {
return new ObfuscatedNameSupplier(renameStrategy, previousMappings);
}
}
private static NameSupplier createNameSupplier(
RenameStrategy renameStrategy, RenamingMap mappings) {
checkState(renameStrategy == RenameStrategy.MAPPED);
return new MappedNameSupplier(mappings);
}
private class GatherGenerators extends AbstractPostOrderCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
JSDocInfo doc = n.getJSDocInfo();
if (doc == null) {
return;
}
int numGeneratorAnnotations =
Booleans.countTrue(
doc.isConsistentIdGenerator(),
doc.isIdGenerator(),
doc.isStableIdGenerator(),
doc.isXidGenerator(),
doc.isMappedIdGenerator());
if (numGeneratorAnnotations == 0) {
return;
} else if (numGeneratorAnnotations > 1) {
compiler.report(t.makeError(n, CONFLICTING_GENERATOR_TYPE));
}
String name = null;
if (n.isAssign()) {
name = n.getFirstChild().getQualifiedName();
} else if (NodeUtil.isNameDeclaration(n)) {
name = n.getFirstChild().getString();
} else if (n.isFunction()){
name = n.getFirstChild().getString();
if (name.isEmpty()) {
return;
}
}
if (doc.isConsistentIdGenerator()) {
consistNameMap.put(name, new LinkedHashMap());
nameGenerators.put(
name, createNameSupplier(
RenameStrategy.CONSISTENT, previousMap.get(name)));
} else if (doc.isStableIdGenerator()) {
nameGenerators.put(
name, createNameSupplier(
RenameStrategy.STABLE, previousMap.get(name)));
} else if (doc.isXidGenerator()) {
nameGenerators.put(
name, createNameSupplier(
RenameStrategy.XID, previousMap.get(name)));
} else if (doc.isIdGenerator()) {
nameGenerators.put(
name, createNameSupplier(
RenameStrategy.INCONSISTENT, previousMap.get(name)));
} else if (doc.isMappedIdGenerator()) {
NameSupplier supplier = nameGenerators.get(name);
if (supplier == null
|| supplier.getRenameStrategy() != RenameStrategy.MAPPED) {
compiler.report(t.makeError(n, MISSING_NAME_MAP_FOR_GENERATOR));
// skip registering the name in the list of Generators if there no
// mapping.
return;
}
} else {
throw new IllegalStateException("unexpected");
}
idGeneratorMaps.put(name, new LinkedHashMap());
}
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverseEs6(compiler, root, new GatherGenerators());
if (!nameGenerators.isEmpty()) {
NodeTraversal.traverseEs6(compiler, root, new ReplaceGenerators());
}
}
private class ReplaceGenerators extends AbstractPostOrderCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (!n.isCall()) {
return;
}
String callName = n.getFirstChild().getQualifiedName();
NameSupplier nameGenerator = nameGenerators.get(callName);
if (nameGenerator == null) {
return;
}
if (!t.inGlobalHoistScope()
&& nameGenerator.getRenameStrategy() == RenameStrategy.INCONSISTENT) {
// Warn about calls not in the global scope.
compiler.report(t.makeError(n, NON_GLOBAL_ID_GENERATOR_CALL));
return;
}
if (nameGenerator.getRenameStrategy() == RenameStrategy.INCONSISTENT) {
for (Node ancestor : n.getAncestors()) {
if (NodeUtil.isControlStructure(ancestor)) {
// Warn about conditional calls.
compiler.report(t.makeError(n, CONDITIONAL_ID_GENERATOR_CALL));
return;
}
}
}
Node arg = n.getSecondChild();
if (arg == null) {
compiler.report(t.makeError(n, INVALID_GENERATOR_PARAMETER));
} else if (arg.isString()) {
String rename = getObfuscatedName(
arg, callName, nameGenerator, arg.getString());
parent.replaceChild(n, IR.string(rename));
t.reportCodeChange();
} else if (arg.isObjectLit()) {
for (Node key : arg.children()) {
if (key.isMemberFunctionDef()) {
compiler.report(t.makeError(n, SHORTHAND_FUNCTION_NOT_SUPPORTED_IN_ID_GEN));
return;
}
if (key.isComputedProp()) {
compiler.report(t.makeError(n, COMPUTED_PROP_NOT_SUPPORTED_IN_ID_GEN));
return;
}
String rename = getObfuscatedName(
key, callName, nameGenerator, key.getString());
key.setString(rename);
// Prevent standard renaming by marking the key as quoted.
key.putBooleanProp(Node.QUOTED_PROP, true);
}
arg.detach();
parent.replaceChild(n, arg);
t.reportCodeChange();
} else {
compiler.report(t.makeError(n, INVALID_GENERATOR_PARAMETER));
}
}
private String getObfuscatedName(
Node id, String callName, NameSupplier nameGenerator, String name) {
String rename = null;
Map idGeneratorMap = idGeneratorMaps.get(callName);
String instanceId = getIdForGeneratorNode(
nameGenerator.getRenameStrategy() != RenameStrategy.INCONSISTENT, id);
if (nameGenerator.getRenameStrategy() == RenameStrategy.CONSISTENT) {
Map entry = consistNameMap.get(callName);
rename = entry.get(instanceId);
if (rename == null) {
rename = nameGenerator.getName(instanceId, name);
entry.put(instanceId, rename);
}
} else {
rename = nameGenerator.getName(instanceId, name);
}
idGeneratorMap.put(rename, instanceId);
return rename;
}
}
/**
* @return The serialize map of generators and their ids and their
* replacements.
*/
public String getSerializedIdMappings() {
return IdMappingUtil.generateSerializedIdMappings(idGeneratorMaps);
}
static String getIdForGeneratorNode(boolean consistent, Node n) {
checkState(n.isString() || n.isStringKey(), n);
if (consistent) {
return n.getString();
} else {
return n.getSourceFileName() + ':' + n.getLineno() + ":" + n.getCharno();
}
}
}