/* * 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 com.google.common.base.Preconditions; 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: * <code> * var x = new Foo(); * x.bar; * </code> * 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: * <code> * /** @param {number} x / * Foo.prototype.bar = goog.abstractMethod; * </code> * 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 nicksantos@google.com (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) { Preconditions.checkNotNull(root); Preconditions.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); } } }