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

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

Go to download

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.

There is a newer version: v20240317
Show newest version
/*
 * Copyright 2016 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.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.javascript.jscomp.DefinitionsRemover.Definition;
import com.google.javascript.jscomp.DefinitionsRemover.ExternalNameOnlyDefinition;
import com.google.javascript.jscomp.DefinitionsRemover.UnknownDefinition;
import com.google.javascript.jscomp.NodeTraversal.Callback;
import com.google.javascript.jscomp.NodeTraversal.ChangeScopeRootCallback;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;

/**
 * Simple name-based definition gatherer.
 *
 * 

It treats all variable writes as happening in the global scope and treats all objects as * capable of having the same set of properties. The current implementation only handles definitions * whose right hand side is an immutable value or function expression. All complex definitions are * treated as unknowns. * *

This definition simply uses the variable name to determine a new definition site so * potentially it could return multiple definition sites for a single variable. Although we could * use the type system to make this more accurate, in practice after disambiguate properties has * run, names are unique enough that this works well enough to accept the performance gain. */ public class NameBasedDefinitionProvider implements CompilerPass { protected final AbstractCompiler compiler; protected final Multimap definitionsByName; protected final Map definitionSitesByDefinitionSiteNode; protected final Multimap definitionSitesByScopeNode; protected final Set definitionNodes; protected final boolean allowComplexFunctionDefs; protected boolean hasProcessBeenRun = false; public NameBasedDefinitionProvider(AbstractCompiler compiler, boolean allowComplexFunctionDefs) { this.compiler = compiler; this.allowComplexFunctionDefs = allowComplexFunctionDefs; int numInputs = compiler.getNumberOfInputs(); // Estimates below were generated by experimentation with large Google projects. this.definitionsByName = LinkedHashMultimap.create(numInputs * 15, 1); int estimatedDefinitionSites = numInputs * 22; this.definitionSitesByDefinitionSiteNode = Maps.newLinkedHashMapWithExpectedSize(estimatedDefinitionSites); this.definitionSitesByScopeNode = HashMultimap.create(estimatedDefinitionSites, 1); this.definitionNodes = Sets.newHashSetWithExpectedSize(estimatedDefinitionSites); } @Override public void process(Node externs, Node source) { checkState(!hasProcessBeenRun, "The definition provider is already initialized."); this.hasProcessBeenRun = true; NodeTraversal.traverseEs6(compiler, externs, new DefinitionGatheringCallback(true)); dropUntypedExterns(); NodeTraversal.traverseEs6(compiler, source, new DefinitionGatheringCallback(false)); } public void rebuildScopeRoots(List changedScopeRoots, List deletedScopeRoots) { for (Node scopeRoot : Iterables.concat(deletedScopeRoots, changedScopeRoots)) { for (DefinitionSite definitionSite : definitionSitesByScopeNode.removeAll(scopeRoot)) { Definition definition = definitionSite.definition; definitionNodes.remove(definitionSite.node); definitionsByName.remove(definition.getSimplifiedName(), definition); definitionSitesByDefinitionSiteNode.remove(definitionSite.node); } } DefinitionGatheringCallback cb = new DefinitionGatheringCallback(); NodeTraversal.traverseEs6ScopeRoots(compiler, null, changedScopeRoots, cb, cb, false); } /** @return Whether the node has a JSDoc that actually declares something. */ private boolean jsdocContainsDeclarations(Node node) { JSDocInfo info = node.getJSDocInfo(); return (info != null && info.containsDeclaration()); } /** * Drop untyped stub definitions (ExternalNameOnlyDefinition) in externs if a typed extern of the * same qualified name also exists and has type annotations. * *

TODO: This hack is mostly for the purpose of preventing untyped stubs from showing up in the * {@link PureFunctionIdentifier} and causing unknown side effects from propagating everywhere. * This should probably be solved in one of the following ways instead: * *

a) Have a pass earlier in the compiler that goes in and removes these stub definitions. * *

b) Fix all extern files so that there are no untyped stubs mixed with typed ones and add a * restriction to the compiler to prevent this. * *

c) Drop these stubs in the {@link PureFunctionIdentifier} instead. This "DefinitionProvider" * should not have to drop definitions itself. */ private void dropUntypedExterns() { for (String name : definitionsByName.keySet()) { for (Definition definition : new ArrayList<>(definitionsByName.get(name))) { if (!(definition instanceof ExternalNameOnlyDefinition)) { continue; } Node definitionNode = definition.getLValue(); if (jsdocContainsDeclarations(definitionNode)) { continue; } for (Definition previousDefinition : definitionsByName.get(name)) { if (previousDefinition != definition && definitionNode.matchesQualifiedName(previousDefinition.getLValue())) { // *DON'T* remove from definitionNodes since it is desired to retain references to // stub definitions. definitionsByName.remove(name, definition); DefinitionSite definitionSite = definitionSitesByDefinitionSiteNode.remove(definitionNode); Node scopeNode = NodeUtil.getEnclosingChangeScopeRoot(definitionNode); definitionSitesByScopeNode.remove(scopeNode, definitionSite); // Since it's a stub we know its keyed by the name/getProp node. checkNotNull(definitionSite); break; } } } } } /** * Returns a collection of definitions that characterize the possible values of a variable or * property. */ public Collection getDefinitionsReferencedAt(Node useSiteNode) { checkState(hasProcessBeenRun, "Hasn't been initialized with process() yet."); checkArgument(useSiteNode.isGetProp() || useSiteNode.isName(), useSiteNode); if (definitionNodes.contains(useSiteNode)) { return ImmutableList.of(); } if (useSiteNode.isGetProp()) { String propName = useSiteNode.getLastChild().getString(); if (propName.equals("apply") || propName.equals("call")) { useSiteNode = useSiteNode.getFirstChild(); } } String name = getSimplifiedName(useSiteNode); if (name != null) { return definitionsByName.get(name); } return ImmutableList.of(); } private class DefinitionGatheringCallback implements Callback, ChangeScopeRootCallback { DefinitionGatheringCallback() {} DefinitionGatheringCallback(boolean inExterns) { this.inExterns = inExterns; } @Override public void enterChangeScopeRoot(AbstractCompiler compiler, Node root) { this.inExterns = root.isFromExterns(); } private boolean inExterns; @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { if (inExterns) { if (n.isFunction() && !n.getFirstChild().isName()) { // No need to crawl functions in JSDoc return false; } if (parent != null && parent.isFunction() && n != parent.getFirstChild()) { // Arguments of external functions should not count as name // definitions. They are placeholder names for documentation // purposes only which are not reachable from anywhere. return false; } } return true; } @Override public void visit(NodeTraversal traversal, Node node, Node parent) { if (inExterns) { visitExterns(traversal, node); } else { visitCode(traversal, node); } } private void visitExterns(NodeTraversal traversal, Node node) { if (node.getJSDocInfo() != null) { for (Node typeRoot : node.getJSDocInfo().getTypeNodes()) { traversal.traverse(typeRoot); } } Definition definition = DefinitionsRemover.getDefinition(node, true); if (definition != null) { String name = definition.getSimplifiedName(); if (name != null) { Node rValue = definition.getRValue(); if ((rValue != null) && !NodeUtil.isImmutableValue(rValue) && !rValue.isFunction()) { // Unhandled complex expression Definition unknownDefinition = new UnknownDefinition(definition.getLValue(), true); definition = unknownDefinition; } addDefinition(name, definition, node, traversal); } } } private void visitCode(NodeTraversal traversal, Node node) { Definition definition = DefinitionsRemover.getDefinition(node, false); if (definition != null) { String name = definition.getSimplifiedName(); if (name != null) { Node rValue = definition.getRValue(); if (rValue != null && !NodeUtil.isImmutableValue(rValue) && !isKnownFunctionDefinition(rValue)) { // Unhandled complex expression definition = new UnknownDefinition(definition.getLValue(), false); } addDefinition(name, definition, node, traversal); } } } boolean isKnownFunctionDefinition(Node n) { switch (n.getToken()) { case FUNCTION: return true; case HOOK: return allowComplexFunctionDefs && isKnownFunctionDefinition(n.getSecondChild()) && isKnownFunctionDefinition(n.getLastChild()); default: return false; } } } private void addDefinition( String name, Definition definition, Node definitionSiteNode, NodeTraversal traversal) { Node definitionNode = definition.getLValue(); definitionNodes.add(definitionNode); definitionsByName.put(name, definition); DefinitionSite definitionSite = new DefinitionSite( definitionSiteNode, definition, traversal.getModule(), traversal.inGlobalScope(), definition.isExtern()); definitionSitesByDefinitionSiteNode.put(definitionSiteNode, definitionSite); Node scopeNode = NodeUtil.getEnclosingChangeScopeRoot(definitionSiteNode); definitionSitesByScopeNode.put(scopeNode, definitionSite); } /** * Extract a name from a node. In the case of GETPROP nodes, replace the namespace or object * expression with "this" for simplicity and correctness at the expense of inefficiencies due to * higher chances of name collisions. * *

TODO(user) revisit. it would be helpful to at least use fully qualified names in the case of * namespaces. Might not matter as much if this pass runs after {@link CollapseProperties}. */ @Nullable public static String getSimplifiedName(Node node) { if (node.isName()) { String name = node.getString(); if (name != null && !name.isEmpty()) { return name; } else { return null; } } else if (node.isGetProp()) { return "this." + node.getLastChild().getString(); } else if (node.isMemberFunctionDef()) { return "this." + node.getString(); } return null; } /** * Returns the collection of definition sites found during traversal. * * @return definition site collection. */ public Collection getDefinitionSites() { checkState(hasProcessBeenRun, "Hasn't been initialized with process() yet."); return definitionSitesByDefinitionSiteNode.values(); } public DefinitionSite getDefinitionForFunction(Node function) { checkState(hasProcessBeenRun, "Hasn't been initialized with process() yet."); checkState(function.isFunction()); return definitionSitesByDefinitionSiteNode.get(NodeUtil.getNameNode(function)); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy