All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.MakeDeclaredNamesUnique Maven / Gradle / Ivy

/*
 * 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.MoreObjects.toStringHelper;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multiset;
import com.google.javascript.jscomp.NodeTraversal.ScopedCallback;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import org.jspecify.nullness.Nullable;

/**
 * Find all Functions, VARs, and Exception names and make them unique. Specifically, it will not
 * modify object properties.
 */
class MakeDeclaredNamesUnique extends NodeTraversal.AbstractScopedCallback {

  // Arguments is special cased to handle cases where a local name shadows
  // the arguments declaration.
  public static final String ARGUMENTS = "arguments";

  // There is one renamer on the stack for each scope. This was added before any support for ES6 was
  // in place, so it was necessary to maintain this separate stack, rather than just using the
  // NodeTraversal's stack of scopes, in order to handle catch blocks and function names correctly.
  private final Deque renamerStack = new ArrayDeque<>();
  private final Renamer rootRenamer;
  private final boolean markChanges;
  private final boolean assertOnChange;

  private MakeDeclaredNamesUnique(Renamer renamer, boolean markChanges, boolean assertOnChange) {
    this.rootRenamer = renamer;
    this.markChanges = markChanges;
    this.assertOnChange = assertOnChange;
  }

  static final class Builder {
    private Renamer renamer;
    private boolean markChanges = true;
    private boolean assertOnChange = false;

    private Builder() {}

    Builder withRenamer(Renamer renamer) {
      this.renamer = renamer;
      return this;
    }

    /**
     * Enables the option to report any code changes to the compiler object.
     *
     * 

This option is {@code true} by default. */ Builder withMarkChanges(boolean markChanges) { this.markChanges = markChanges; return this; } /** * If this class finds any names that are not unique, throws an exception * *

This is intended for use in validating that an AST is already normalized. * *

This option is {@code false} by default. */ Builder withAssertOnChange(boolean assertOnChange) { this.assertOnChange = assertOnChange; return this; } MakeDeclaredNamesUnique build() { if (renamer == null) { renamer = new ContextualRenamer(); } return new MakeDeclaredNamesUnique(renamer, markChanges, assertOnChange); } } static Builder builder() { return new Builder(); } static CompilerPass getContextualRenameInverter(AbstractCompiler compiler) { return new ContextualRenameInverter(compiler); } @Override public void enterScope(NodeTraversal t) { Node declarationRoot = t.getScopeRoot(); Renamer renamer; if (renamerStack.isEmpty()) { // If the contextual renamer is being used, the starting context can not // be a function. checkState(!declarationRoot.isFunction() || !(rootRenamer instanceof ContextualRenamer)); renamer = rootRenamer; } else { boolean hoist = !declarationRoot.isFunction() && !NodeUtil.createsBlockScope(declarationRoot); renamer = renamerStack.peek().createForChildScope(t.getScopeRoot(), hoist); } renamerStack.push(renamer); findDeclaredNames(t, declarationRoot); } @Override public void exitScope(NodeTraversal t) { if (!t.inGlobalScope()) { renamerStack.pop(); } } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getToken()) { case NAME: case IMPORT_STAR: visitNameOrImportStar(t, n, parent); break; default: break; } } private void visitNameOrImportStar(NodeTraversal t, Node n, Node parent) { // Don't rename the exported name foo in export {a as foo}; or import {foo as b}; if (n.isName() && NodeUtil.isNonlocalModuleExportName(n)) { return; } String newName = getReplacementName(n.getString()); if (newName == null) { return; } if (assertOnChange) { throw new IllegalStateException( "Unexpected renaming in MakeDeclaredNamesUnique. new name: " + newName + " for " + n); } Renamer renamer = renamerStack.peek(); if (renamer.stripConstIfReplaced()) { n.putBooleanProp(Node.IS_CONSTANT_NAME, false); Node jsDocInfoNode = NodeUtil.getBestJSDocInfoNode(n); if (jsDocInfoNode != null && jsDocInfoNode.getJSDocInfo() != null) { JSDocInfo.Builder builder = JSDocInfo.Builder.copyFrom(jsDocInfoNode.getJSDocInfo()); builder.recordMutable(); jsDocInfoNode.setJSDocInfo(builder.build()); } } n.setString(newName); if (markChanges) { t.reportCodeChange(); // If we are renaming a function declaration, make sure the containing scope // has the opportunity to act on the change. if (parent.isFunction() && NodeUtil.isFunctionDeclaration(parent)) { t.getCompiler().reportChangeToEnclosingScope(parent); } } } /** Walks the stack of name maps and finds the replacement name for the current scope. */ private @Nullable String getReplacementName(String oldName) { for (Renamer renamer : renamerStack) { String newName = renamer.getReplacementName(oldName); if (newName != null) { return newName; } } return null; } /** * Traverses the current scope and collects declared names by calling {@code addDeclaredName} on * the {@code Renamer} that is at the top of the {@code renamerStack}. */ private void findDeclaredNames(NodeTraversal t, Node n) { checkState(NodeUtil.createsScope(n) || n.isScript(), n); for (Var v : t.getScope().getVarIterable()) { renamerStack.peek().addDeclaredName(v.getName(), false); } } /** Declared names renaming policy interface. */ interface Renamer { /** * Called when a declared name is found in the local current scope. * * @param hoisted Whether this name should be declared in the nearest enclosing "hoist scope" * instead of the scope represented by this Renamer. */ void addDeclaredName(String name, boolean hoisted); /** * @return A replacement name, null if oldName is unknown or should not be replaced. */ String getReplacementName(String oldName); /** * @return Whether the constant-ness of a name should be removed. */ boolean stripConstIfReplaced(); /** * @param hoisted True if this is a "hoist" scope: A function, module, or global scope. * @return A Renamer for a scope within the scope of the current Renamer. */ Renamer createForChildScope(Node scopeRoot, boolean hoisted); /** * @return The closest hoisting target for var and function declarations. */ Renamer getHoistRenamer(); } /** Inverts the transformation by {@link ContextualRenamer}, when possible. */ static class ContextualRenameInverter implements ScopedCallback, CompilerPass { private final AbstractCompiler compiler; /** * The top of this stack is always an object storing information about the current variable * scope or {@code null} for the global scope. */ private final Deque scopeContextStack = new ArrayDeque<>(); /** * Keeps track of all {@code VariableInfo} objects for all variables visible in the current * variable scope. * *

Basically this is a map whose keys are names and whose values are stacks of {@code * VariableInfo} objects. The top of each stack represents the variable with that name that is * visible in the scope we are currently traversing. */ private final VariableInfoStackMap variablesStackedByName = new VariableInfoStackMap(); private ContextualRenameInverter(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node js) { NodeTraversal.traverse(compiler, js, this); } public static String getOriginalName(String name) { int index = indexOfSeparator(name); return (index <= 0) ? name : name.substring(0, index); } private static int indexOfSeparator(String name) { return name.lastIndexOf(ContextualRenamer.UNIQUE_ID_SEPARATOR); } /** Prepare a set for the new scope. */ @Override public void enterScope(NodeTraversal t) { if (t.inGlobalScope()) { // Don't track any information for the global scope, because it is likely to be // very large, and we will never rename any variables it contains. MakeDeclaredNamesUnique // favors keeping global variables unchanged, so they won't need to be restored. return; } final Scope scope = t.getScope(); // Create DeclaredVariableInfo objects for all variables declared in this scope. // Push them onto the variablesByName data structure to make them visible, // and store them into a ScopeContext object for the scope we're entering. final ImmutableList.Builder declaredVariablesBuilder = ImmutableList.builder(); for (Var var : scope.getVarIterable()) { DeclaredVariableInfo variableInfo = createDeclaredVariableInfo(var); declaredVariablesBuilder.add(variableInfo); variablesStackedByName.pushVariableInfo(variableInfo); } ScopeContext parent = scopeContextStack.peek(); scopeContextStack.push(new ScopeContext(scope, declaredVariablesBuilder.build(), parent)); } private DeclaredVariableInfo createDeclaredVariableInfo(Var var) { final String currentName = var.getName(); int indexOfSeparator = indexOfSeparator(currentName); final RenamingInfo renamingInfo; if (indexOfSeparator > 0) { // We check for an index > 0, because a variable created directly by the compiler generally // begin with `$jscomp$` (the separator). We can't and don't want to try to rename any // variable to an empty string. final String invertedName = currentName.substring(0, indexOfSeparator); renamingInfo = new RenamingInfo(currentName, invertedName); } else { renamingInfo = null; } return new DeclaredVariableInfo(var, renamingInfo); } /** Keep track of information needed to rename a {@code DeclaredVariableInfo}. */ class RenamingInfo { /** Will be filled with all nodes referring to the variable. */ private final List referenceNodes = new ArrayList<>(); /** * Will be filled with objects representing variables whose names must not conflict with this * one. */ private final Set potentialShadowVariables = new LinkedHashSet<>(); /** The name the variable has currently */ private final String currentName; /** The name we want to use when renaming the variable. */ private final String preferredName; RenamingInfo(String currentName, String preferredName) { this.currentName = currentName; this.preferredName = preferredName; } void addPotentialShadowVariable(VariableInfo variableInfo) { potentialShadowVariables.add(variableInfo); } String attemptRename() { final Set disallowedNames = new LinkedHashSet<>(); // If we somehow ended up with "arguments$jscomp$..." it's not safe to rename // that to "arguments", because that name is special, but it would still be good // to simplify its name, if possible. See the note below regarding why we // rename variables to something other than their original name. disallowedNames.add(ARGUMENTS); for (VariableInfo potentialShadowVariable : potentialShadowVariables) { // NOTE: We need to look up these names immediately before making our rename // decision, because some of these variables could themselves have been renamed. final String potentialShadowName = potentialShadowVariable.getName(); disallowedNames.add(potentialShadowName); } String newName = preferredName; if (disallowedNames.contains(preferredName)) { // Why are we bothering to find a name other than the original one? // The reason is that we would like the final output name to depend more on the shape // of the output code than on the process the compiler followed to reach that shape. // This keeps our unit tests more stable. Without it, a tweak to the details of some // optimization pass would be more likely to make a trivial change to the output that // breaks unit tests. final String baseName = preferredName + ContextualRenamer.UNIQUE_ID_SEPARATOR; int i = 0; do { newName = baseName + i++; } while (disallowedNames.contains(newName)); } // It's possible we ended up generating the same name we already had. if (newName.equals(currentName)) { return currentName; } for (Node referenceNode : referenceNodes) { referenceNode.setString(newName); // NOTE: This could probably be made more efficient by only reporting after doing all the // nodes that occur in the same scope. compiler.reportChangeToEnclosingScope(referenceNode); Node parent = referenceNode.getParent(); // If we are renaming a function declaration, make sure the containing scope // has the opportunity to act on the change. if (parent.isFunction() && NodeUtil.isFunctionDeclaration(parent)) { compiler.reportChangeToEnclosingScope(parent); } } return newName; } } /** Records information about a variable for which we have seen a non-global declaration. */ private static class DeclaredVariableInfo implements VariableInfo { final Var var; String name; /** * Keep track of information needed to rename this variable, if that is possible. * *

This will be {@code null} if renaming has already been done or cannot be done. */ private @Nullable RenamingInfo renamingInfo; DeclaredVariableInfo(Var var, @Nullable RenamingInfo renamingInfo) { this.var = var; this.name = var.getName(); this.renamingInfo = renamingInfo; } @Override public String getName() { return name; } @Override public int getScopeDepth() { return var.getScope().getDepth(); } @Override public void addPotentialConflict(VariableInfo variableInfo) { if (renamingInfo != null && variableInfo != this) { renamingInfo.addPotentialShadowVariable(variableInfo); } // else we won't rename, so we don't care. } @Override public void addReferenceNode(Node nameNode) { if (renamingInfo != null) { renamingInfo.referenceNodes.add(nameNode); } // else we won't rename, so we don't care } private void tryToInvertName() { if (renamingInfo != null) { // update the name for the sake of other variables that may check this one for // conflicts. name = renamingInfo.attemptRename(); renamingInfo = null; // allow garbage collection } } } /** * Rename vars for the current scope, and merge any referenced names into the parent scope * reference set. */ @Override public void exitScope(NodeTraversal t) { if (t.inGlobalScope()) { return; } ScopeContext scopeContext = checkNotNull(scopeContextStack.pop()); // Tell all the variables we referenced and the variables that might conflict with them // about each other. // This needs to happen before we start trying to rename the variables in this scope next. for (VariableInfo referencedVariable : scopeContext.referencedVariables) { scopeContext.recordPotentialConflicts(referencedVariable); } // Pop each variable declared in the scope we're exiting off of the variable stack // data structure. // Try to rename each one. for (DeclaredVariableInfo declaredVariableInfo : scopeContext.declaredVariableInfos) { final String variableName = declaredVariableInfo.getName(); // Out of an abundance of caution: // 1. Pop the variable from its name stack and make sure it matches // 2. Do this before renaming causes the name to be different. VariableInfo oldStackTop = variablesStackedByName.popVariableInfo(variableName); checkState( oldStackTop == declaredVariableInfo, "Declared variable \"%s\" was not the top of the name stack", variableName); declaredVariableInfo.tryToInvertName(); } if (scopeContextStack.isEmpty()) { // clear the records for any global or undeclared variables before we start traversing // a new local scope. variablesStackedByName.clear(); } } @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { return true; } @Override public void visit(NodeTraversal t, Node node, Node parent) { ScopeContext scopeContext = scopeContextStack.peek(); if (scopeContext != null) { // We're not in the global scope if (NodeUtil.isReferenceName(node) || node.isImportStar()) { final String name = node.getString(); // Get the corresponding VariableInfo, creating one if we haven't found a declaration // for it. final VariableInfo variableInfo = variablesStackedByName.getOrCreateCurrentVariableInfo(name); // add this reference to the variable variableInfo.addReferenceNode(node); // tell the current scope that it references the variable scopeContext.addReferencedVariableInfo(variableInfo); } } // else nothing to do for the global scope } /** * Keep track of information about variables declared and referenced in a local scope. * *

We do not create one of these objects for the global scope, because we know those won't be * renamed. It isn't worthwhile to track all of them. */ private static class ScopeContext { final @Nullable ScopeContext parent; final Scope scope; final ImmutableList declaredVariableInfos; /** Will be filled with objects for all variables we reference in this scope. */ final Set referencedVariables = new LinkedHashSet<>(); private ScopeContext( Scope scope, ImmutableList declaredVariableInfos, @Nullable ScopeContext parent) { this.scope = scope; this.declaredVariableInfos = declaredVariableInfos; this.parent = parent; } public void addReferencedVariableInfo(VariableInfo variableInfo) { referencedVariables.add(variableInfo); } public void recordPotentialConflicts(VariableInfo referencedVariable) { int thisScopeDepth = scope.getDepth(); int referencedVariableDeclarationDepth = referencedVariable.getScopeDepth(); if (thisScopeDepth >= referencedVariableDeclarationDepth) { for (DeclaredVariableInfo declaredVariableInfo : declaredVariableInfos) { // NOTE: No need to check whether the 2 variables are actually the same here. // The logic in addPotentialConflict() can do that more efficiently. // The referenced variable must be sure not to "hide behind" another variable // declared between its declaration and the location where it is referenced. referencedVariable.addPotentialConflict(declaredVariableInfo); // The declared variable must be sure not to "shadow" a variable declared in its own // or a higher scope, thus preventing the reference from seeing it. declaredVariableInfo.addPotentialConflict(referencedVariable); } if (parent != null) { parent.recordPotentialConflicts(referencedVariable); } } } } /** * Handle storing and looking up {@code VariableInfo} objects for the variables that are visible * in the current {@code ScopeContext}. */ private class VariableInfoStackMap { final Map> mapOfStacks = new LinkedHashMap<>(); /** Push a {@code VariableInfo} onto the stack corresponding to its name. */ void pushVariableInfo(VariableInfo info) { final String name = info.getName(); Deque variableInfoStack = getVariableInfoStack(name); variableInfoStack.push(info); } private Deque getVariableInfoStack(String name) { return mapOfStacks.computeIfAbsent(name, ignoredKey -> new ArrayDeque<>()); } VariableInfo getOrCreateCurrentVariableInfo(String name) { final Deque variableInfoStack = getVariableInfoStack(name); final VariableInfo variableInfo; if (variableInfoStack.isEmpty()) { variableInfo = createUndeclaredVariableInfo(name); variableInfoStack.push(variableInfo); } else { variableInfo = variableInfoStack.peek(); } return variableInfo; } /** Pop the topmost {@code VariableInfo} off of the stack for the given variable name. */ VariableInfo popVariableInfo(String name) { Deque variableInfos = mapOfStacks.get(name); checkNotNull(variableInfos, "Nonexistent variable info requested: '%s'", name); VariableInfo info = variableInfos.pop(); if (variableInfos.isEmpty()) { // Remove empty stacks, so they can be garbage collected. mapOfStacks.remove(name); } return info; } /** Empty all the variable name stacks. */ void clear() { mapOfStacks.clear(); } } private VariableInfo createUndeclaredVariableInfo(String name) { return new UndeclaredVariableInfo(name); } /** * Represent a variable we've seen referenced but not declared. * *

One of these will be created when a global variable or simply undeclared variable is * referenced. It doesn't do much more than keep track of the name and the fact that we don't * have a declaration for it (as a 0 scope depth). */ private static class UndeclaredVariableInfo implements VariableInfo { final String name; private UndeclaredVariableInfo(String name) { this.name = name; } @Override public void addReferenceNode(Node nameNode) { // no need to track references } @Override public String getName() { return name; } @Override public int getScopeDepth() { // NOTE: Pretend that any variable for which we didn't see a declaration was declared in // the global scope, which is depth 0. This will most often be actually true, since we skip // creating variables for the global scope to avoid wasting time and space on variables that // will never be renamed. return 0; } @Override public void addPotentialConflict(VariableInfo variableInfo) { // No need to record the potential conflict, because this variable will never be renamed. } } /** One of these will be created for every variable we encounter. */ interface VariableInfo { void addReferenceNode(Node nameNode); String getName(); int getScopeDepth(); void addPotentialConflict(VariableInfo variableInfo); } } /** * Renames every local name to be unique. The first encountered declaration of a given name * (specifically a global declaration) is left in its original form. Those that are renamed are * made unique by giving them a unique suffix based on the number of declarations of the name. * *

The root ContextualRenamer is assumed to be in GlobalScope. * *

Used by the Normalize pass. * * @see Normalize */ static class ContextualRenamer implements Renamer { private final @Nullable Node scopeRoot; // This multiset is shared between this ContextualRenamer and its parent (and its parent's // parent, etc.) because it tracks counts of variables across the entire JS program. private final Multiset nameUsage; // By contrast, this is a different map for each ContextualRenamer because it's just keeping // track of the names used by this renamer. private final Map declarations = new LinkedHashMap<>(); private final boolean global; private final Renamer hoistRenamer; static final String UNIQUE_ID_SEPARATOR = "$jscomp$"; @Override public String toString() { return toStringHelper(this) .add("scopeRoot", scopeRoot) .add("nameUsage", nameUsage) .add("declarations", declarations) .add("global", global) .toString(); } ContextualRenamer() { scopeRoot = null; global = true; nameUsage = HashMultiset.create(); hoistRenamer = this; } /** Constructor for child scopes. */ private ContextualRenamer( Node scopeRoot, Multiset nameUsage, boolean hoistingTargetScope, Renamer parent) { checkState(NodeUtil.createsScope(scopeRoot), scopeRoot); if (scopeRoot.isFunction()) { checkState(!hoistingTargetScope, scopeRoot); } this.scopeRoot = scopeRoot; this.global = false; this.nameUsage = nameUsage; if (hoistingTargetScope) { checkState(!NodeUtil.createsBlockScope(scopeRoot), scopeRoot); hoistRenamer = this; } else { checkState(NodeUtil.createsBlockScope(scopeRoot) || scopeRoot.isFunction(), scopeRoot); hoistRenamer = parent.getHoistRenamer(); } } /** Create a ContextualRenamer */ @Override public Renamer createForChildScope(Node scopeRoot, boolean hoistingTargetScope) { return new ContextualRenamer(scopeRoot, nameUsage, hoistingTargetScope, this); } /** Adds a name to the map of names declared in this scope. */ @Override public void addDeclaredName(String name, boolean hoisted) { if (hoisted && hoistRenamer != this) { hoistRenamer.addDeclaredName(name, true); } else { if (!name.equals(ARGUMENTS)) { if (global) { reserveName(name); } else { // It hasn't been declared locally yet, so increment the count. if (!declarations.containsKey(name)) { int id = incrementNameCount(name); String newName = null; if (id != 0) { // this name is exists at another source location, create another // The stack of renamers traverse all scopes and track all declared // names and their counts. This means, they could generate conflicting names if it // does not see the entire program (e.g. when used via library level checks). // Currently, the renamer stack is only used by Normalize and ScopedAliases pass. // 1. When used in normalize, renamer stack see the whole program and tracks the // name counts. Hence, when generating a unique name, it's guaranteed to generate // names unique across the entire program. // 2. When used in ScopedAliases, the renamer stack sees only the current library // sources. But, in this use case, is only renames locals within a function body. // Hence it does not generate conflicting declarations. // We could stop tracking the counts and switch the renamer to use the file hashcode // based UniqueIdSupplier, but that requires updating several unit tests that run // normalize. newName = getUniqueName(name, id); while (nameUsage.contains(newName)) { // if the newName also exists at another location, create another id = incrementNameCount(newName); newName = getUniqueName(name, id); } reserveName(newName); // reserve this new name so that it's never reused } declarations.put(name, newName); } } } } } @Override public String getReplacementName(String oldName) { return declarations.get(oldName); } /** Given a name and the associated id, create a new unique name. */ private static String getUniqueName(String name, int id) { return name + UNIQUE_ID_SEPARATOR + id; } private void reserveName(String name) { nameUsage.setCount(name, 0, 1); } private int incrementNameCount(String name) { return nameUsage.add(name, 1); } @Override public boolean stripConstIfReplaced() { return false; } @Override public Renamer getHoistRenamer() { return hoistRenamer; } } /** * Rename every declared name to be unique. Typically, this would be used when injecting code to * ensure that names do not conflict with existing names. * *

Used by the FunctionInjector * * @see FunctionInjector */ static class InlineRenamer implements Renamer { private final Map declarations = new LinkedHashMap<>(); private final Supplier uniqueIdSupplier; private final String idPrefix; private final boolean removeConstness; private final CodingConvention convention; private final Renamer hoistRenamer; InlineRenamer( CodingConvention convention, Supplier uniqueIdSupplier, String idPrefix, boolean removeConstness, boolean hoistingTargetScope, Renamer parent) { this.convention = convention; this.uniqueIdSupplier = uniqueIdSupplier; // To ensure that the id does not conflict with the id from the // ContextualRenamer some prefix is needed. checkArgument(!idPrefix.isEmpty()); this.idPrefix = idPrefix; this.removeConstness = removeConstness; if (hoistingTargetScope) { hoistRenamer = this; } else { hoistRenamer = parent.getHoistRenamer(); } } @Override public void addDeclaredName(String name, boolean hoisted) { checkState(!name.equals(ARGUMENTS)); if (hoisted && hoistRenamer != this) { hoistRenamer.addDeclaredName(name, hoisted); } else { declarations.computeIfAbsent(name, this::getUniqueName); } } private String getUniqueName(String name) { if (name.isEmpty()) { return name; } if (name.contains(ContextualRenamer.UNIQUE_ID_SEPARATOR)) { name = name.substring(0, name.lastIndexOf(ContextualRenamer.UNIQUE_ID_SEPARATOR)); } if (convention.isExported(name)) { // The google internal coding convention includes a naming convention // to export names starting with "_". Simply strip "_" those to avoid // exporting names. name = "JSCompiler_" + name; } // By using the same separator the id will be stripped if it isn't // needed when variable renaming is turned off. return name + ContextualRenamer.UNIQUE_ID_SEPARATOR + idPrefix + uniqueIdSupplier.get(); } @Override public String getReplacementName(String oldName) { return declarations.get(oldName); } @Override public Renamer createForChildScope(Node scopeRoot, boolean hoistingTargetScope) { return new InlineRenamer( convention, uniqueIdSupplier, idPrefix, removeConstness, hoistingTargetScope, this); } @Override public boolean stripConstIfReplaced() { return removeConstness; } @Override public Renamer getHoistRenamer() { return hoistRenamer; } } /** * For injecting boilerplate libraries. Leaves global names alone and renames local names like * InlineRenamer. */ static class BoilerplateRenamer extends ContextualRenamer { private final Supplier uniqueIdSupplier; private final String idPrefix; private final CodingConvention convention; BoilerplateRenamer( CodingConvention convention, Supplier uniqueIdSupplier, String idPrefix) { this.convention = convention; this.uniqueIdSupplier = uniqueIdSupplier; this.idPrefix = idPrefix; } @Override public Renamer createForChildScope(Node scopeRoot, boolean hoisted) { return new InlineRenamer(convention, uniqueIdSupplier, idPrefix, false, hoisted, this); } } /** Only rename things that match specific names. Wraps another renamer. */ static class TargettedRenamer implements Renamer { private final Renamer delegate; private final Set targets; TargettedRenamer(Renamer delegate, Set targets) { this.delegate = delegate; this.targets = targets; } @Override public void addDeclaredName(String name, boolean hoisted) { if (targets.contains(name)) { delegate.addDeclaredName(name, hoisted); } } @Override public @Nullable String getReplacementName(String oldName) { return targets.contains(oldName) ? delegate.getReplacementName(oldName) : null; } @Override public boolean stripConstIfReplaced() { return delegate.stripConstIfReplaced(); } @Override public Renamer createForChildScope(Node scopeRoot, boolean hoistingTargetScope) { return new TargettedRenamer( delegate.createForChildScope(scopeRoot, hoistingTargetScope), targets); } @Override public Renamer getHoistRenamer() { return delegate.getHoistRenamer(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy