com.google.javascript.jscomp.InferJSDocInfo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of closure-compiler Show documentation
Show all versions of closure-compiler Show documentation
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.
/*
* 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.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.EnumType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.ObjectType;
import javax.annotation.Nullable;
/**
* Set the JSDocInfo on all types.
*
* Propagates JSDoc across the type graph, but not across the symbol graph.
* This means that if you have:
*
* var x = new Foo();
* x.bar;
*
* then the JSType attached to x.bar may get associated JSDoc, but the
* Node and Var will not.
*
* JSDoc is initially attached to AST Nodes at parse time.
* There are 3 ways that JSDoc get propagated across the type system.
* 1) Nominal types (e.g., constructors) may contain JSDocInfo for their
* declaration.
* 2) Object types have a JSDocInfo slot for each property on that type.
* 3) Shape types (like structural functions) may have JSDocInfo.
*
* #1 and #2 should be self-explanatory, and non-controversial. #3 is
* a bit trickier. It means that if you have:
*
* /** @param {number} x /
* Foo.prototype.bar = goog.abstractMethod;
*
* the JSDocInfo will appear in two places in the type system: in the 'bar'
* slot of Foo.prototype, and on the function expression type created by
* this expression.
*
* @author [email protected] (Nick Santos)
*/
class InferJSDocInfo extends AbstractPostOrderCallback
implements HotSwapCompilerPass {
private final AbstractCompiler compiler;
InferJSDocInfo(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
if (externs != null) {
NodeTraversal.traverseEs6(compiler, externs, this);
}
if (root != null) {
NodeTraversal.traverseEs6(compiler, root, this);
}
}
@Override
public void hotSwapScript(Node root, Node originalRoot) {
checkNotNull(root);
checkState(root.isScript());
NodeTraversal.traverseEs6(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
JSDocInfo docInfo;
switch (n.getToken()) {
// Infer JSDocInfo on types of all type declarations on variables.
case NAME:
if (parent == null) {
return;
}
// Only allow JSDoc on VARs, function declarations, and assigns.
if (!parent.isVar() &&
!NodeUtil.isFunctionDeclaration(parent) &&
!(parent.isAssign() &&
n == parent.getFirstChild())) {
return;
}
// There are four places the doc info could live.
// 1) A FUNCTION node.
// /** ... */ function f() { ... }
// 2) An ASSIGN parent.
// /** ... */ x = function () { ... }
// 3) A NAME parent.
// var x, /** ... */ y = function() { ... }
// 4) A VAR grandparent.
// /** ... */ var x = function() { ... }
docInfo = n.getJSDocInfo();
if (docInfo == null &&
!(parent.isVar() &&
!parent.hasOneChild())) {
docInfo = parent.getJSDocInfo();
}
// Try to find the type of the NAME.
JSType varType = n.getJSType();
if (varType == null && parent.isFunction()) {
varType = parent.getJSType();
}
// If we have no type to attach JSDocInfo to, then there's nothing
// we can do.
if (varType == null || docInfo == null) {
return;
}
// Dereference the type. If the result is not an object, or already
// has docs attached, then do nothing.
ObjectType objType = dereferenceToObject(varType);
if (objType == null || objType.getJSDocInfo() != null) {
return;
}
attachJSDocInfoToNominalTypeOrShape(objType, docInfo, n.getString());
break;
case STRING_KEY:
case GETTER_DEF:
case SETTER_DEF:
docInfo = n.getJSDocInfo();
if (docInfo == null) {
return;
}
ObjectType owningType = dereferenceToObject(parent.getJSType());
if (owningType != null) {
String propName = n.getString();
if (owningType.hasOwnProperty(propName)) {
owningType.setPropertyJSDocInfo(propName, docInfo);
}
}
break;
case GETPROP:
// Infer JSDocInfo on properties.
// There are two ways to write doc comments on a property.
//
// 1)
// /** @deprecated */
// obj.prop = ...
//
// 2)
// /** @deprecated */
// obj.prop;
if (parent.isExprResult() ||
(parent.isAssign() &&
parent.getFirstChild() == n)) {
docInfo = n.getJSDocInfo();
if (docInfo == null) {
docInfo = parent.getJSDocInfo();
}
if (docInfo != null) {
ObjectType lhsType =
dereferenceToObject(n.getFirstChild().getJSType());
if (lhsType != null) {
// Put the JSDoc in the property slot, if there is one.
String propName = n.getLastChild().getString();
if (lhsType.hasOwnProperty(propName)) {
lhsType.setPropertyJSDocInfo(propName, docInfo);
}
// Put the JSDoc in any constructors or function shapes as well.
ObjectType propType =
dereferenceToObject(lhsType.getPropertyType(propName));
if (propType != null) {
attachJSDocInfoToNominalTypeOrShape(
propType, docInfo, n.getQualifiedName());
}
}
}
}
break;
default:
break;
}
}
/**
* Dereferences the given type to an object, or returns null.
*/
private static ObjectType dereferenceToObject(JSType type) {
return ObjectType.cast(type == null ? null : type.dereference());
}
/**
* Handle cases #1 and #3 in the class doc.
*/
private static void attachJSDocInfoToNominalTypeOrShape(
ObjectType objType, JSDocInfo docInfo, @Nullable String qName) {
if (objType.isConstructor() ||
objType.isEnumType() ||
objType.isInterface()) {
// Named types.
if (objType.hasReferenceName() &&
objType.getReferenceName().equals(qName)) {
objType.setJSDocInfo(docInfo);
if (objType.isConstructor() || objType.isInterface()) {
JSType.toMaybeFunctionType(objType).getInstanceType().setJSDocInfo(docInfo);
} else if (objType instanceof EnumType) {
((EnumType) objType).getElementsType().setJSDocInfo(docInfo);
}
}
} else if (!objType.isNativeObjectType() && objType.isFunctionType()) {
// Structural functions.
objType.setJSDocInfo(docInfo);
}
}
}