From a6217b5da210bc1d3649d9b86e282409f5703552 Mon Sep 17 00:00:00 2001 From: nickreid Date: Wed, 18 Jul 2018 10:38:55 -0700 Subject: [PATCH] Updates `InferJSDocInfo` to handle ES6 class syntax. 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 --- .../javascript/jscomp/InferJSDocInfo.java | 74 ++-- .../javascript/jscomp/InferJSDocInfoTest.java | 396 ++++++++++++++---- 2 files changed, 352 insertions(+), 118 deletions(-) diff --git a/src/com/google/javascript/jscomp/InferJSDocInfo.java b/src/com/google/javascript/jscomp/InferJSDocInfo.java index cac2f7c5fc3..984f9e39315 100644 --- a/src/com/google/javascript/jscomp/InferJSDocInfo.java +++ b/src/com/google/javascript/jscomp/InferJSDocInfo.java @@ -22,12 +22,14 @@ 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.FunctionType; import com.google.javascript.rhino.jstype.JSType; import com.google.javascript.rhino.jstype.ObjectType; 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. * *

This pass propagates JSDocs across the type graph, but not across the symbol graph. For * example: @@ -56,9 +58,23 @@ * why we support this.) * * - *

#2 should be self-explanatory. #1 is also fairly straight-forward with the additional detail - * that JSDocs are propagated to both the instance type and the declaration type (i.e. the - * ctor or enum object). #3 is a bit trickier; it covers types such as the following declarations: + *

#1 is fairly straight-forward with the additional detail that JSDocs are propagated to both + * the instance type and the declaration type (i.e. the ctor or enum type). #2 should also + * be mostly self-explanatory; it covers scenarios like the following: + * + *

{@code
+ * /**
+ *  * I'm a method!
+ *  * @param {number} x
+ *  * @return {number}
+ *  *\/
+ * Foo.prototype.bar = function(x) { ... };
+ * }
+ * + * 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: * *
{@code
  * /** I'm an anonymous structural object type! *\/
@@ -74,21 +90,9 @@
  * }
* * 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 - * types cover the following scenario: - * - *
{@code
- * /**
- *  * I'm a method with a novel function type!
- *  * @param {number} x
- *  * @return {number}
- *  *\/
- * Foo.prototype.bar = function(x) { ... };
- * }
- * - * 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. + * the same structure will get different JSDocs despite possibly comparing equal. Additionally, when + * 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. * * @author nicksantos@google.com (Nick Santos) */ @@ -126,7 +130,7 @@ public void visit(NodeTraversal t, Node n, Node parent) { 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 JSType inferredType; if (NodeUtil.isNameDeclaration(parent)) { @@ -136,8 +140,10 @@ public void visit(NodeTraversal t, Node n, Node parent) { typeDoc = (nameInfo != null) ? nameInfo : parent.getJSDocInfo(); inferredType = n.getJSType(); - } else if (NodeUtil.isFunctionDeclaration(parent)) { + } else if (NodeUtil.isFunctionDeclaration(parent) + || NodeUtil.isClassDeclaration(parent)) { // Case: `/** ... */ function f() { ... }`. + // Case: `/** ... */ class Foo() { ... }`. typeDoc = parent.getJSDocInfo(); inferredType = parent.getJSType(); } else if (parent.isAssign() && n.isFirstChildOf(parent)) { @@ -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. - ObjectType objType = dereferenceToObject(inferredType); + ObjectType objType = dereferenced(inferredType); if (objType == null || objType.getJSDocInfo() != null) { return; } @@ -172,7 +178,17 @@ public void visit(NodeTraversal t, Node n, Node parent) { 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) { return; } @@ -203,7 +219,7 @@ public void visit(NodeTraversal t, Node n, Node parent) { return; } - ObjectType lhsType = dereferenceToObject(n.getFirstChild().getJSType()); + ObjectType lhsType = dereferenced(n.getFirstChild().getJSType()); if (lhsType == null) { return; } @@ -215,7 +231,7 @@ public void visit(NodeTraversal t, Node n, Node parent) { } // 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) { attachJSDocInfoToNominalTypeOrShape(propType, typeDoc, n.getQualifiedName()); } @@ -227,11 +243,9 @@ public void visit(NodeTraversal t, Node n, Node parent) { } } - /** - * Dereferences the given type to an object, or returns null. - */ - private static ObjectType dereferenceToObject(JSType type) { - return ObjectType.cast(type == null ? null : type.dereference()); + /** Nullsafe wrapper for {@code JSType#dereference()}. */ + private static ObjectType dereferenced(@Nullable JSType type) { + return type == null ? null : type.dereference(); } /** Handle cases #1 and #3 in the class doc. */ diff --git a/test/com/google/javascript/jscomp/InferJSDocInfoTest.java b/test/com/google/javascript/jscomp/InferJSDocInfoTest.java index 6235e224bcb..06e50fdc326 100644 --- a/test/com/google/javascript/jscomp/InferJSDocInfoTest.java +++ b/test/com/google/javascript/jscomp/InferJSDocInfoTest.java @@ -16,13 +16,9 @@ package com.google.javascript.jscomp; -import static com.google.common.truth.Truth.assertThat; import static com.google.javascript.jscomp.parsing.Config.JsDocParsing.INCLUDE_DESCRIPTIONS_NO_WHITESPACE; import com.google.javascript.jscomp.CompilerOptions.LanguageMode; -import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; -import com.google.javascript.jscomp.NodeTraversal.Callback; -import com.google.javascript.rhino.JSDocInfo.Visibility; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.jstype.FunctionType; import com.google.javascript.rhino.jstype.JSType; @@ -55,8 +51,6 @@ public String toJs() { } } - private TypedScope globalScope; - @Override protected CompilerOptions getOptions() { CompilerOptions options = super.getOptions(); @@ -66,16 +60,6 @@ protected CompilerOptions getOptions() { return options; } - private final Callback callback = new AbstractPostOrderCallback() { - @Override - public void visit(NodeTraversal t, Node n, Node parent) { - TypedScope s = t.getTypedScope(); - if (s.isGlobal()) { - globalScope = s; - } - } - }; - @Override protected CompilerPass getProcessor(final Compiler compiler) { return new CompilerPass() { @@ -88,9 +72,6 @@ public void process(Node externs, Node root) { compiler, compiler.getReverseAbstractInterpreter(), topScope, scopeCreator) .process(externs, root); - NodeTraversal t = new NodeTraversal(compiler, callback, scopeCreator); - t.traverseRoots(externs, root); - new InferJSDocInfo(compiler).process(externs, root); } }; @@ -177,6 +158,24 @@ public void testJSDocFromNamedFunctionPropagatesToDefinedType() { assertEquals("I'm a user type.", xType.getJSDocInfo().getBlockDescription()); } + public void testJSDocFromNamedEs6ClassPropagatesToDefinedType() { + // Given + testSame( + srcs( + lines( + "/** I'm a user type. */", + "class Foo { };", + "", + "var x = new Foo();" // Just a hook to access type "Foo". + ))); + + JSType xType = inferredTypeOfName("x"); + assertEquals("Foo", xType.toString()); + + // Then + assertEquals("I'm a user type.", xType.getJSDocInfo().getBlockDescription()); + } + public void testJSDocFromGlobalAssignmentPropagatesToDefinedType() { // Given testSame( @@ -275,7 +274,7 @@ public void testJSDocFromVariableNameAssignmentPropagatesToDefinedType() { assertEquals("I'm a user type.", xType.getJSDocInfo().getBlockDescription()); } - public void testJSDocIsPropagatedToClasses() { + public void testJSDocIsPropagatedToClasses_Es5() { // Given testSame( srcs( @@ -296,6 +295,24 @@ public void testJSDocIsPropagatedToClasses() { assertEquals("I'm a user class.", xType.getJSDocInfo().getBlockDescription()); } + public void testJSDocIsPropagatedToClasses_Es6() { + // Given + testSame( + srcs( + lines( + "/** I'm a user class. */", + "var Foo = class { };", + "", + "var x = new Foo();" // Just a hook to access type "Foo". + ))); + + JSType xType = inferredTypeOfName("x"); + assertEquals("Foo", xType.toString()); + + // Then + assertEquals("I'm a user class.", xType.getJSDocInfo().getBlockDescription()); + } + public void testJSDocIsPropagatedToCtors() { // Given testSame( @@ -338,6 +355,27 @@ public void testJSDocIsPropagatedToInterfaces() { assertEquals("I'm a user interface.", xType.getJSDocInfo().getBlockDescription()); } + public void testJSDocIsPropagatedToRecords() { + // Given + testSame( + srcs( + lines( + "/**", + " * I'm a user record.", + " * @record", + " */", + "var Foo = function() {};", + "", + "var x = /** @type {!Foo} */ ({});" // Just a hook to access type "Foo". + ))); + + JSType xType = inferredTypeOfName("x"); + assertEquals("Foo", xType.toString()); + + // Then + assertEquals("I'm a user record.", xType.getJSDocInfo().getBlockDescription()); + } + public void testJSDocIsPropagatedToEnums() { // Given testSame( @@ -402,6 +440,28 @@ public void testJSDocIsPropagatedToFunctionTypes() { assertEquals("I'm a custom function.", xType.getJSDocInfo().getBlockDescription()); } + public void testJSDocIsPropagatedToScopedTypes() { + // Given + testSame( + srcs( + lines( + "(() => {", + " /**", + " * I'm a scoped user class.", + " * @constructor", + " */", + " var Foo = function() {};", + "", + " var x = new Foo();", // Just a hook to access type "Foo". + "})();"))); + + JSType xType = inferredTypeOfName("x"); + assertEquals("Foo", xType.toString()); + + // Then + assertEquals("I'm a scoped user class.", xType.getJSDocInfo().getBlockDescription()); + } + public void testJSDocIsNotPropagatedToFunctionTypesFromMethodAssigments() { // Given testSame( @@ -426,9 +486,7 @@ public void testJSDocIsNotPropagatedToFunctionTypesFromMethodAssigments() { assertNull(xType.getJSDocInfo()); } - // TODO(b/111070482): Why is this expected? Why are there multiple type instances? This can cause - // non-determinism. - public void testJSDocIsPropagatedDistinctlyToStructuralTypes_ObjectLiteralTypes() { + public void testJSDocIsPropagatedDistinctlyToMatchingStructuralTypes_ObjectLiteralTypes() { // Given testSame( srcs( @@ -438,8 +496,8 @@ public void testJSDocIsPropagatedDistinctlyToStructuralTypes_ObjectLiteralTypes( " */", "var test0 = {a: 4, b: 5};", "", - // The type of this object *looks* is the same, but it is different (for some - // reason) and we should get a different JSDoc. + // The type of this object *looks* is the same, but it is different since the type + // may get more properties later. Therefore, it should get a different JSDoc. "/**", " * I'm test1.", " */", @@ -447,6 +505,7 @@ public void testJSDocIsPropagatedDistinctlyToStructuralTypes_ObjectLiteralTypes( JSType test0Type = inferredTypeOfName("test0"); JSType test1Type = inferredTypeOfName("test1"); + // For some reason `test0Type` and `test1Type` aren't equal. This is good, but unexpected. assertNotSame(test0Type, test1Type); // Then @@ -454,9 +513,7 @@ public void testJSDocIsPropagatedDistinctlyToStructuralTypes_ObjectLiteralTypes( assertEquals("I'm test1.", test1Type.getJSDocInfo().getBlockDescription()); } - // TODO(b/111070482): Why is this expected? Why are there multiple type instances? This can cause - // non-determinism. - public void testJSDocIsPropagatedDistinctlyToStructuralTypes_FunctionTypes() { + public void testJSDocIsPropagatedDistinctlyToMatchingStructuralTypes_FunctionTypes() { // Given testSame( srcs( @@ -468,8 +525,8 @@ public void testJSDocIsPropagatedDistinctlyToStructuralTypes_FunctionTypes() { " */", "function test0(a) {};", "", - // The type of this function *looks* is the same, but it is different (for some - // reason) and we should get a different JSDoc. + // The type of this function *looks* is the same, but it is different since the type + // may get more properties later. Therefore, it should get a different JSDoc. "/**", " * I'm test1.", " * @param {*} a", @@ -479,6 +536,9 @@ public void testJSDocIsPropagatedDistinctlyToStructuralTypes_FunctionTypes() { JSType test0Type = inferredTypeOfName("test0"); JSType test1Type = inferredTypeOfName("test1"); + // We really only care that they match, not about equality. + // TODO(b/111070482): That fact that these are equal yet have different JSDocs is bad. + assertEquals(test0Type, test1Type); assertNotSame(test0Type, test1Type); // Then @@ -486,40 +546,6 @@ public void testJSDocIsPropagatedDistinctlyToStructuralTypes_FunctionTypes() { assertEquals("I'm test1.", test1Type.getJSDocInfo().getBlockDescription()); } - // TODO(nickreid): The comments in `InferJSDocInfo` claim the opposite of this test should be - // true, but as it stands, this is what happens. - public void testJSDocPropagatesDistinctlyToStructuralTypes_FunctionAndMethodTypes() { - // Given - testSame( - srcs( - lines( - "/** @constructor */", - "function Foo() {};", - "", - "/**", - " * I'm a free function.", - " * @return {number}", - " */", - "function free() { return 0; }", - "", - "/**", - " * I'm a method.", - " * @return {*} a", - " */", - "Foo.prototype.method = free;", - "", - "var x = new Foo();" // Just a hook to access type "Foo". - ))); - - JSType freeType = inferredTypeOfName("free"); - ObjectType xType = (ObjectType) inferredTypeOfName("x"); - assertNotSame(freeType, xType); - - // Then - assertEquals("I'm a free function.", freeType.getJSDocInfo().getBlockDescription()); - assertEquals("I'm a method.", xType.getPropertyJSDocInfo("method").getBlockDescription()); - } - public void testJSDocIsNotOverriddenByStructuralTypeAssignments_ObjectLiteralTypes() { // Given testSame( @@ -576,7 +602,7 @@ public void testJSDocIsNotOverriddenByStructuralTypeAssignments_FunctionTypes() assertEquals("I'm test0.", test0Type.getJSDocInfo().getBlockDescription()); } - public void testJSDocIsPropagatedToFieldProperties() { + public void testJSDocIsPropagatedToFieldProperties_Es5() { // Given testSame( srcs( @@ -600,7 +626,32 @@ public void testJSDocIsPropagatedToFieldProperties() { assertEquals("I'm a field.", xType.getPropertyJSDocInfo("field").getBlockDescription()); } - public void testJSDocIsPropagatedToGetterProperties() { + public void testJSDocIsPropagatedToFieldProperties_Es6Class() { + // Given + testSame( + srcs( + lines( + "class Foo {", + " constructor() {", + " /**", + " * I'm a field.", + " * @const", + " */", + " this.field = 5;", + " }", + "}", + "", + "var x = new Foo();" // Just a hook to access type "Foo". + ))); + + ObjectType xType = (ObjectType) inferredTypeOfName("x"); + assertEquals("Foo", xType.toString()); + + // Then + assertEquals("I'm a field.", xType.getPropertyJSDocInfo("field").getBlockDescription()); + } + + public void testJSDocIsPropagatedToGetterProperties_Es5() { // Given testSame( srcs( @@ -626,7 +677,30 @@ public void testJSDocIsPropagatedToGetterProperties() { assertEquals("I'm a getter.", xType.getPropertyJSDocInfo("getter").getBlockDescription()); } - public void testJSDocIsPropagatedToSetterProperties() { + public void testJSDocIsPropagatedToGetterProperties_Es6Class() { + // Given + testSame( + srcs( + lines( + "class Foo {", + " /**", + " * I'm a getter.", + " * @return {number}", + " */", + " get getter() {}", + "}", + "", + "var x = new Foo();" // Just a hook to access type "Foo". + ))); + + ObjectType xType = (ObjectType) inferredTypeOfName("x"); + assertEquals("Foo", xType.toString()); + + // Then + assertEquals("I'm a getter.", xType.getPropertyJSDocInfo("getter").getBlockDescription()); + } + + public void testJSDocIsPropagatedToSetterProperties_Es5() { // Given testSame( srcs( @@ -652,7 +726,30 @@ public void testJSDocIsPropagatedToSetterProperties() { assertEquals("I'm a setter.", xType.getPropertyJSDocInfo("setter").getBlockDescription()); } - public void testJSDocIsPropagatedToMethodProperties() { + public void testJSDocIsPropagatedToSetterProperties_Es6Class() { + // Given + testSame( + srcs( + lines( + "class Foo {", + " /**", + " * I'm a setter.", + " * @param {number} a", + " */", + " set setter(a) {}", + "}", + "", + "var x = new Foo();" // Just a hook to access type "Foo". + ))); + + ObjectType xType = (ObjectType) inferredTypeOfName("x"); + assertEquals("Foo", xType.toString()); + + // Then + assertEquals("I'm a setter.", xType.getPropertyJSDocInfo("setter").getBlockDescription()); + } + + public void testJSDocIsPropagatedToMethodProperties_Es5() { // Given testSame( srcs( @@ -676,7 +773,30 @@ public void testJSDocIsPropagatedToMethodProperties() { assertEquals("I'm a method.", xType.getPropertyJSDocInfo("method").getBlockDescription()); } - public void testJSDocIsPropagatedToAbstractMethodProperties() { + public void testJSDocIsPropagatedToMethodProperties_Es6Class() { + // Given + testSame( + srcs( + lines( + "class Foo {", + " /**", + " * I'm a method.", + " * @return {number} a", + " */", + " method() { }", + "}", + "", + "var x = new Foo();" // Just a hook to access type "Foo". + ))); + + ObjectType xType = (ObjectType) inferredTypeOfName("x"); + assertEquals("Foo", xType.toString()); + + // Then + assertEquals("I'm a method.", xType.getPropertyJSDocInfo("method").getBlockDescription()); + } + + public void testJSDocIsPropagatedToAbstractMethodProperties_Es5() { // Given testSame( srcs( @@ -701,7 +821,31 @@ public void testJSDocIsPropagatedToAbstractMethodProperties() { assertEquals("I'm a method.", xType.getPropertyJSDocInfo("method").getBlockDescription()); } - public void testJSDocIsPropagatedToStaticProperties() { + public void testJSDocIsPropagatedToAbstractMethodProperties_Es6Class() { + // Given + testSame( + srcs( + lines( + "class Foo {", + " /**", + " * I'm a method.", + " * @return {number} a", + " * @abstract", + " */", + " method() { }", + "}", + "", + "var x = new Foo();" // Just a hook to access type "Foo". + ))); + + ObjectType xType = (ObjectType) inferredTypeOfName("x"); + assertEquals("Foo", xType.toString()); + + // Then + assertEquals("I'm a method.", xType.getPropertyJSDocInfo("method").getBlockDescription()); + } + + public void testJSDocIsPropagatedToStaticProperties_Es5() { // Given testSame( srcs( @@ -725,6 +869,86 @@ public void testJSDocIsPropagatedToStaticProperties() { assertEquals("I'm a static.", xType.getPropertyJSDocInfo("static").getBlockDescription()); } + public void testJSDocIsPropagatedToStaticProperties_Es6Class() { + // Given + testSame( + srcs( + lines( + "class Foo {", + " /**", + " * I'm a static.", + " * @return {number} a", + " */", + " static static() { }", + "}", + "", + "var x = Foo;" // Just a hook to access type "ctor{Foo}". + ))); + + ObjectType xType = (ObjectType) inferredTypeOfName("x"); + assertEquals("function(new:Foo): undefined", xType.toString()); + + // Then + assertEquals("I'm a static.", xType.getPropertyJSDocInfo("static").getBlockDescription()); + } + + // TODO(b/30710701): Constructor docs should be used in some way. This is probably similar to how + // access control is being differentiated between constructor invocation and constructors as + // namespaces. The decision for both of these cases should be made together. + public void testJSDocFromConstructorsIsIgnored_Es6Class() { + // Given + testSame( + srcs( + lines( + "/** I'm a class. */", + "class Foo {", + " /** I'm a constructor. */", + " constructor() { }", + "}", + "", + "var x = new Foo();" // Just a hook to access type "Foo". + ))); + + ObjectType xType = (ObjectType) inferredTypeOfName("x"); + assertEquals("Foo", xType.toString()); + + // Then + assertEquals("I'm a class.", xType.getJSDocInfo().getBlockDescription()); + assertNull(xType.getPropertyJSDocInfo("constructor")); + } + + public void testJSDocDoesNotPropagateFromStructuralTypesToClassProperties() { + // Given + testSame( + srcs( + lines( + "/** @constructor */", + "function Foo() {};", + "", + "/**", + " * I'm a free function.", + " * @return {number}", + " */", + "function free() { return 0; }", + "", + "/**", + " * I'm a method.", + " * @return {number} a", + " */", + "Foo.prototype.method = free;", + "", + "var x = new Foo();" // Just a hook to access type "Foo". + ))); + + JSType freeType = inferredTypeOfName("free"); + ObjectType xType = (ObjectType) inferredTypeOfName("x"); + assertNotSame(freeType, xType); + + // Then + assertEquals("I'm a free function.", freeType.getJSDocInfo().getBlockDescription()); + assertEquals("I'm a method.", xType.getPropertyJSDocInfo("method").getBlockDescription()); + } + public void testJSDocDoesNotPropagateBackwardFromInstancesToTypes() { // Given testSame( @@ -795,42 +1019,38 @@ public void testJSDocFromDuplicateDefinitionIsUsedIfThereWasNoOriginalJSDocFromO assertEquals("I'm a different type.", xType.getJSDocInfo().getBlockDescription()); } - public void testJSDocIsPropagatedToTypeFromPrototypeObjectLiteral() { + public void testJSDocIsPropagatedToTypeFromObjectLiteralPrototype() { testSame( lines( "/** @constructor */", "function Foo() {}", "", "Foo.prototype = {", - " /** @protected */ a: function() {},", - " /** @protected */ get b() {},", - " /** @protected */ set c(x) {},", - " /** @protected */ d() {}", + " /** Property a. */ a: function() {},", + " /** Property b. */ get b() {},", + " /** Property c. */ set c(x) {},", + " /** Property d. */ d() {}", "};")); FunctionType ctor = inferredTypeOfName("Foo").toMaybeFunctionType(); ObjectType prototype = ctor.getInstanceType().getImplicitPrototype(); - assertThat(prototype.getOwnPropertyJSDocInfo("a").getVisibility()) - .isEqualTo(Visibility.PROTECTED); - assertThat(prototype.getOwnPropertyJSDocInfo("b").getVisibility()) - .isEqualTo(Visibility.PROTECTED); - assertThat(prototype.getOwnPropertyJSDocInfo("c").getVisibility()) - .isEqualTo(Visibility.PROTECTED); - assertThat(prototype.getOwnPropertyJSDocInfo("d").getVisibility()) - .isEqualTo(Visibility.PROTECTED); + assertEquals("Property a.", prototype.getOwnPropertyJSDocInfo("a").getBlockDescription()); + assertEquals("Property b.", prototype.getOwnPropertyJSDocInfo("b").getBlockDescription()); + assertEquals("Property c.", prototype.getOwnPropertyJSDocInfo("c").getBlockDescription()); + assertEquals("Property d.", prototype.getOwnPropertyJSDocInfo("d").getBlockDescription()); } - /** Returns the inferred type of the reference {@code name} in the global scope. */ + /** Returns the inferred type of the reference {@code name} anywhere in the AST. */ private JSType inferredTypeOfName(String name) { - return inferredTypeHavingScopedName(name, globalScope); + return inferredTypeOfLocalName(name, getLastCompiler().getRoot()); } - /** Returns the inferred type of the reference {@code name} in {@code scope}. */ - private JSType inferredTypeHavingScopedName(String name, TypedScope scope) { - Node root = scope.getRootNode(); + /** Returns the inferred type of the reference {@code name} under {@code root} AST node.. */ + private JSType inferredTypeOfLocalName(String name, Node root) { Deque queue = new ArrayDeque<>(); queue.push(root); + while (!queue.isEmpty()) { Node current = queue.pop(); if (current.matchesQualifiedName(name) && current.getJSType() != null) {