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 com.google.common.base.Preconditions;
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.LinkedHashMap;
import java.util.Map;

/**
 * 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 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) { Preconditions.checkState(!hasProcessBeenRun, "The definition provider is already initialized."); this.hasProcessBeenRun = true; NodeTraversal.traverseEs6(compiler, externs, new DefinitionGatheringCallback(true)); NodeTraversal.traverseEs6(compiler, source, new DefinitionGatheringCallback(false)); } @Override public Collection getDefinitionsReferencedAt(Node useSite) { Preconditions.checkState(hasProcessBeenRun, "The process was not run"); Preconditions.checkArgument(useSite.isGetProp() || useSite.isName()); if (definitionNodeByDefinitionSite.containsKey(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; } // TODO(johnlenz) : remove this stub dropping code if it becomes // illegal to have untyped stubs in the externs definitions. // We need special handling of untyped externs stubs here: // the stub should be dropped if the name is provided elsewhere. // If there is no qualified name for this, then there will be // no stubs to remove. This will happen if node is an object // literal key. if (node.isQualifiedName()) { for (Definition prevDef : new ArrayList<>(nameDefinitionMultimap.get(name))) { if (prevDef instanceof ExternalNameOnlyDefinition && !jsdocContainsDeclarations(node)) { if (node.matchesQualifiedName(prevDef.getLValue())) { // Drop this stub, there is a real definition. nameDefinitionMultimap.remove(name, prevDef); } } } } addDefinition(name, def, node, traversal); } } if (parent != null && parent.isExprResult()) { String name = getSimplifiedName(node); if (name != null) { // TODO(johnlenz) : remove this code if it becomes illegal to have // stubs in the externs definitions. // We need special handling of untyped externs stubs here: // the stub should be dropped if the name is provided elsewhere. // We can't just drop the stub now as it needs to be used as the // externs definition if no other definition is provided. boolean dropStub = false; if (!jsdocContainsDeclarations(node) && node.isQualifiedName()) { for (Definition prevDef : nameDefinitionMultimap.get(name)) { if (node.matchesQualifiedName(prevDef.getLValue())) { dropStub = true; break; } } } if (!dropStub) { // Incomplete definition Definition definition = new ExternalNameOnlyDefinition(node); addDefinition(name, definition, 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; } } /** @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()); } } private void addDefinition(String name, Definition def, Node node, NodeTraversal traversal) { 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() { Preconditions.checkState(hasProcessBeenRun, "The process was not run"); return definitionNodeByDefinitionSite.values(); } public DefinitionSite getDefinitionForFunction(Node function) { Preconditions.checkState(hasProcessBeenRun, "The process was not run"); Preconditions.checkState(function.isFunction()); return definitionNodeByDefinitionSite.get(NodeUtil.getNameNode(function)); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy