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. This binary checks for style issues such as incorrect or missing JSDoc usage, and missing goog.require() statements. It does not do more advanced checks such as typechecking.

There is a newer version: v20200830
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 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.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) { Preconditions.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. Preconditions.checkNotNull(site); break; } } } } } } } @Override public Collection getDefinitionsReferencedAt(Node useSite) { Preconditions.checkState(hasProcessBeenRun, "The process was not run"); Preconditions.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() { 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 - 2024 Weber Informatics LLC | Privacy Policy