Skip to content

Commit

Permalink
Updates InferJSDocInfo to handle ES6 class syntax.
Browse files Browse the repository at this point in the history
Specifically, JSDoc is now propagated from:
  - ES6 class declarations
  - ES6 class property declarations

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=205100276
  • Loading branch information
nreid260 authored and tjgq committed Jul 18, 2018
1 parent 90d5fb1 commit a6217b5
Show file tree
Hide file tree
Showing 2 changed files with 352 additions and 118 deletions.
74 changes: 44 additions & 30 deletions src/com/google/javascript/jscomp/InferJSDocInfo.java
Expand Up @@ -22,12 +22,14 @@
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType; import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.ObjectType; import com.google.javascript.rhino.jstype.ObjectType;
import javax.annotation.Nullable; import javax.annotation.Nullable;


/** /**
* Sets the {@link JSDocInfo} on all {@code JSType}s using the JSDoc on the node defining that type. * Sets the {@link JSDocInfo} on all {@code JSType}s, including their properties, using the JSDoc on
* the node defining that type or property.
* *
* <p>This pass propagates JSDocs across the type graph, but not across the symbol graph. For * <p>This pass propagates JSDocs across the type graph, but not across the symbol graph. For
* example: * example:
Expand Down Expand Up @@ -56,9 +58,23 @@
* why we support this.) * why we support this.)
* </ul> * </ul>
* *
* <p>#2 should be self-explanatory. #1 is also fairly straight-forward with the additional detail * <p>#1 is fairly straight-forward with the additional detail that JSDocs are propagated to both
* that JSDocs are propagated to both the instance type <em>and</em> the declaration type (i.e. the * the instance type <em>and</em> the declaration type (i.e. the ctor or enum type). #2 should also
* ctor or enum object). #3 is a bit trickier; it covers types such as the following declarations: * be mostly self-explanatory; it covers scenarios like the following:
*
* <pre>{@code
* /**
* * I'm a method!
* * @param {number} x
* * @return {number}
* *\/
* Foo.prototype.bar = function(x) { ... };
* }</pre>
*
* in which JSDocInfo will appear on the "bar" slot of `Foo.prototype` and `Foo`. The function type
* used as the RHS of the assignments (i.e. `function(this:Foo, number): number`) is not considered.
* Note that this example would work equally well if `bar` were declared abstract. #3 is a bit
* trickier; it covers types such as the following declarations:
* *
* <pre>{@code * <pre>{@code
* /** I'm an anonymous structural object type! *\/ * /** I'm an anonymous structural object type! *\/
Expand All @@ -74,21 +90,9 @@
* }</pre> * }</pre>
* *
* which define unique types with their own JSDoc attributes. Object literal or function types with * which define unique types with their own JSDoc attributes. Object literal or function types with
* the same structure will get different JSDocs despite comparing equal. Additionally, structural * the same structure will get different JSDocs despite possibly comparing equal. Additionally, when
* types cover the following scenario: * assigning instances of these types as properties of nominal types (e.g. using `myFunction` as the
* * RHS of #2) the structural type JSDoc plays no part.
* <pre>{@code
* /**
* * I'm a method with a novel function type!
* * @param {number} x
* * @return {number}
* *\/
* Foo.prototype.bar = function(x) { ... };
* }</pre>
*
* the JSDocInfo will appear in two places in the type system: in the "bar" slot of `Foo.prototype`,
* and on the specific instance of type `function(this:Foo, number): number`. Note that this example
* would work equally well if `bar` were declared abstract.
* *
* @author nicksantos@google.com (Nick Santos) * @author nicksantos@google.com (Nick Santos)
*/ */
Expand Down Expand Up @@ -126,7 +130,7 @@ public void visit(NodeTraversal t, Node n, Node parent) {
return; return;
} }


// Only allow JSDoc on variable declarations, named functions, and assigns. // Only allow JSDoc on variable declarations, named functions, named classes, and assigns.
final JSDocInfo typeDoc; final JSDocInfo typeDoc;
final JSType inferredType; final JSType inferredType;
if (NodeUtil.isNameDeclaration(parent)) { if (NodeUtil.isNameDeclaration(parent)) {
Expand All @@ -136,8 +140,10 @@ public void visit(NodeTraversal t, Node n, Node parent) {
typeDoc = (nameInfo != null) ? nameInfo : parent.getJSDocInfo(); typeDoc = (nameInfo != null) ? nameInfo : parent.getJSDocInfo();


inferredType = n.getJSType(); inferredType = n.getJSType();
} else if (NodeUtil.isFunctionDeclaration(parent)) { } else if (NodeUtil.isFunctionDeclaration(parent)
|| NodeUtil.isClassDeclaration(parent)) {
// Case: `/** ... */ function f() { ... }`. // Case: `/** ... */ function f() { ... }`.
// Case: `/** ... */ class Foo() { ... }`.
typeDoc = parent.getJSDocInfo(); typeDoc = parent.getJSDocInfo();
inferredType = parent.getJSType(); inferredType = parent.getJSType();
} else if (parent.isAssign() && n.isFirstChildOf(parent)) { } else if (parent.isAssign() && n.isFirstChildOf(parent)) {
Expand All @@ -153,7 +159,7 @@ public void visit(NodeTraversal t, Node n, Node parent) {
} }


// If we have no type, or the type already has a JSDocInfo, then we're done. // If we have no type, or the type already has a JSDocInfo, then we're done.
ObjectType objType = dereferenceToObject(inferredType); ObjectType objType = dereferenced(inferredType);
if (objType == null || objType.getJSDocInfo() != null) { if (objType == null || objType.getJSDocInfo() != null) {
return; return;
} }
Expand All @@ -172,7 +178,17 @@ public void visit(NodeTraversal t, Node n, Node parent) {
return; return;
} }


ObjectType owningType = dereferenceToObject(parent.getJSType()); final ObjectType owningType;
if (parent.isClassMembers()) {
FunctionType ctorType = (FunctionType) parent.getParent().getJSType();
if (ctorType == null) {
return;
}

owningType = n.isStaticMember() ? ctorType : ctorType.getPrototype();
} else {
owningType = dereferenced(parent.getJSType());
}
if (owningType == null) { if (owningType == null) {
return; return;
} }
Expand Down Expand Up @@ -203,7 +219,7 @@ public void visit(NodeTraversal t, Node n, Node parent) {
return; return;
} }


ObjectType lhsType = dereferenceToObject(n.getFirstChild().getJSType()); ObjectType lhsType = dereferenced(n.getFirstChild().getJSType());
if (lhsType == null) { if (lhsType == null) {
return; return;
} }
Expand All @@ -215,7 +231,7 @@ public void visit(NodeTraversal t, Node n, Node parent) {
} }


// Put the JSDoc in any constructors or function shapes as well. // Put the JSDoc in any constructors or function shapes as well.
ObjectType propType = dereferenceToObject(lhsType.getPropertyType(propName)); ObjectType propType = dereferenced(lhsType.getPropertyType(propName));
if (propType != null) { if (propType != null) {
attachJSDocInfoToNominalTypeOrShape(propType, typeDoc, n.getQualifiedName()); attachJSDocInfoToNominalTypeOrShape(propType, typeDoc, n.getQualifiedName());
} }
Expand All @@ -227,11 +243,9 @@ public void visit(NodeTraversal t, Node n, Node parent) {
} }
} }


/** /** Nullsafe wrapper for {@code JSType#dereference()}. */
* Dereferences the given type to an object, or returns null. private static ObjectType dereferenced(@Nullable JSType type) {
*/ return type == null ? null : type.dereference();
private static ObjectType dereferenceToObject(JSType type) {
return ObjectType.cast(type == null ? null : type.dereference());
} }


/** Handle cases #1 and #3 in the class doc. */ /** Handle cases #1 and #3 in the class doc. */
Expand Down

0 comments on commit a6217b5

Please sign in to comment.