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

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

/*
 * 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.LinkedHashMultimap;
import com.google.common.collect.Multimap;
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.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

/**
 * Simple name-based definition gatherer that implements {@link DefinitionProvider}.
 *
 * 

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 DefinitionProvider, CompilerPass { protected final Multimap nameDefinitionMultimap = LinkedHashMultimap.create(); protected final Map definitionNodeByDefinitionSite = new LinkedHashMap<>(); protected final Set definitionNodes = new HashSet<>(); protected final AbstractCompiler compiler; protected final boolean allowComplexFunctionDefs; protected boolean hasProcessBeenRun = false; public NameBasedDefinitionProvider(AbstractCompiler compiler, boolean allowComplexFunctionDefs) { this.compiler = compiler; this.allowComplexFunctionDefs = allowComplexFunctionDefs; } @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)); } /** @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 unkown side effects from propagating everywhere. * This should probably be solved in one of the following ways instead: * *

a) Have a pass ealier 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 externName : nameDefinitionMultimap.keySet()) { for (Definition def : new ArrayList(nameDefinitionMultimap.get(externName))) { if (def instanceof ExternalNameOnlyDefinition) { Node node = def.getLValue(); if (!jsdocContainsDeclarations(node)) { for (Definition prevDef : nameDefinitionMultimap.get(externName)) { if (prevDef != def && node.matchesQualifiedName(prevDef.getLValue())) { nameDefinitionMultimap.remove(externName, def); DefinitionSite site = definitionNodeByDefinitionSite.remove(def.getLValue()); // Since it's a stub we know its keyed by the name/getProp node. checkNotNull(site); break; } } } } } } } @Override public Collection getDefinitionsReferencedAt(Node useSite) { checkState(hasProcessBeenRun, "The process was not run"); checkArgument(useSite.isGetProp() || useSite.isName()); if (definitionNodes.contains(useSite)) { return null; } if (useSite.isGetProp()) { String propName = useSite.getLastChild().getString(); if (propName.equals("apply") || propName.equals("call")) { useSite = useSite.getFirstChild(); } } String name = getSimplifiedName(useSite); if (name != null) { Collection defs = nameDefinitionMultimap.get(name); return defs.isEmpty() ? null : defs; } return null; } private class DefinitionGatheringCallback implements Callback { private final boolean inExterns; DefinitionGatheringCallback(boolean inExterns) { this.inExterns = 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, parent); } else { visitCode(traversal, node); } } private void visitExterns(NodeTraversal traversal, Node node, Node parent) { if (node.getJSDocInfo() != null) { for (Node typeRoot : node.getJSDocInfo().getTypeNodes()) { traversal.traverse(typeRoot); } } Definition def = DefinitionsRemover.getDefinition(node, true); if (def != null) { String name = getSimplifiedName(def.getLValue()); if (name != null) { Node rValue = def.getRValue(); if ((rValue != null) && !NodeUtil.isImmutableValue(rValue) && !rValue.isFunction()) { // Unhandled complex expression Definition unknownDef = new UnknownDefinition(def.getLValue(), true); def = unknownDef; } addDefinition(name, def, node, traversal); } } } private void visitCode(NodeTraversal traversal, Node node) { Definition def = DefinitionsRemover.getDefinition(node, false); if (def != null) { String name = getSimplifiedName(def.getLValue()); if (name != null) { Node rValue = def.getRValue(); if (rValue != null && !NodeUtil.isImmutableValue(rValue) && !isKnownFunctionDefinition(rValue)) { // Unhandled complex expression def = new UnknownDefinition(def.getLValue(), false); } addDefinition(name, def, 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 def, Node node, NodeTraversal traversal) { definitionNodes.add(def.getLValue()); nameDefinitionMultimap.put(name, def); definitionNodeByDefinitionSite.put( node, new DefinitionSite( node, def, traversal.getModule(), traversal.inGlobalScope(), def.isExtern())); } /** * 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}. */ protected 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(); } return null; } /** * Returns the collection of definition sites found during traversal. * * @return definition site collection. */ public Collection getDefinitionSites() { checkState(hasProcessBeenRun, "The process was not run"); return definitionNodeByDefinitionSite.values(); } public DefinitionSite getDefinitionForFunction(Node function) { checkState(hasProcessBeenRun, "The process was not run"); checkState(function.isFunction()); return definitionNodeByDefinitionSite.get(NodeUtil.getNameNode(function)); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy