/*
Copyright 2013-2016 Jason Leyba
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.github.jsdossier;
import static com.github.jsdossier.TypeInspector.fakeNodeForType;
import static com.github.jsdossier.testing.CompilerUtil.createSourceFile;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import com.github.jsdossier.jscomp.NominalType;
import com.github.jsdossier.proto.BaseProperty;
import com.github.jsdossier.proto.Comment;
import com.github.jsdossier.proto.Function;
import com.github.jsdossier.proto.Function.Detail;
import com.github.jsdossier.proto.Tags;
import com.github.jsdossier.proto.TypeExpression;
import com.github.jsdossier.proto.Visibility;
import com.github.jsdossier.testing.Bug;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.FunctionType;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for extracting function information with a {@link TypeInspector}.
*/
@RunWith(JUnit4.class)
public class TypeInspectorInstanceMethodTest extends AbstractTypeInspectorTest {
/**
* Simulates the definition of goog.abstractMethod, which is not a built-in automatically
* handled by the closure compiler.
*/
private static final String DEFINE_ABSTRACT_METHOD =
"/** @type {!Function} */ var abstractMethod = function() {};";
@Test
public void extractsFunctionDataFromPrototype() {
compile(
"/** @constructor */",
"function A() {}",
"",
"/**",
" * Says hello.",
" * @param {string} name The person to greet.",
" * @return {string} A greeting.",
" * @throws {Error} If the person does not exist.",
" */",
"A.prototype.sayHi = function(name) { return 'Hello, ' + name; };");
NominalType a = typeRegistry.getType("A");
TypeInspector typeInspector = typeInspectorFactory.create(a);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("sayHi")
.setSource(sourceFile("source/foo.js.src.html", 10))
.setDescription(htmlComment("<p>Says hello.</p>\n")))
.addParameter(Detail.newBuilder()
.setName("name")
.setType(stringTypeExpression())
.setDescription(htmlComment("<p>The person to greet.</p>\n")))
.setReturn(Detail.newBuilder()
.setType(stringTypeExpression())
.setDescription(htmlComment("<p>A greeting.</p>\n")))
.addThrown(Detail.newBuilder()
.setType(nullableErrorTypeExpression())
.setDescription(htmlComment("<p>If the person does not exist.</p>\n")))
.build());
}
@Test
@Bug(43)
public void extractsFunctionDataFromPrototype_forInterface() {
compile(
"/** @interface */",
"function A() {}",
"",
"/**",
" * Says hello.",
" * @param {string} name The person to greet.",
" * @return {string} A greeting.",
" * @throws {Error} If the person does not exist.",
" */",
"A.prototype.sayHi = function(name) {};");
NominalType a = typeRegistry.getType("A");
TypeInspector typeInspector = typeInspectorFactory.create(a);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("sayHi")
.setSource(sourceFile("source/foo.js.src.html", 10))
.setDescription(htmlComment("<p>Says hello.</p>\n")))
.addParameter(Detail.newBuilder()
.setName("name")
.setType(stringTypeExpression())
.setDescription(htmlComment("<p>The person to greet.</p>\n")))
.setReturn(Detail.newBuilder()
.setType(stringTypeExpression())
.setDescription(htmlComment("<p>A greeting.</p>\n")))
.addThrown(Detail.newBuilder()
.setType(nullableErrorTypeExpression())
.setDescription(htmlComment("<p>If the person does not exist.</p>\n")))
.build());
}
@Test
@Bug(43)
public void extractsFunctionDataFromPrototype_interfaceFunctionDeclaredButNotAssigned() {
compile(
"/** @interface */",
"function A() {}",
"",
"/**",
" * Says hello.",
" * @param {string} name The person to greet.",
" * @return {string} A greeting.",
" * @throws {Error} If the person does not exist.",
" */",
"A.prototype.sayHi;");
NominalType a = typeRegistry.getType("A");
TypeInspector typeInspector = typeInspectorFactory.create(a);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("sayHi")
.setSource(sourceFile("source/foo.js.src.html", 10))
.setDescription(htmlComment("<p>Says hello.</p>\n")))
.addParameter(Detail.newBuilder()
.setName("name")
.setType(stringTypeExpression())
.setDescription(htmlComment("<p>The person to greet.</p>\n")))
.setReturn(Detail.newBuilder()
.setType(stringTypeExpression())
.setDescription(htmlComment("<p>A greeting.</p>\n")))
.addThrown(Detail.newBuilder()
.setType(nullableErrorTypeExpression())
.setDescription(htmlComment("<p>If the person does not exist.</p>\n")))
.build());
}
@Test
public void extractsFunctionDataFromConstructor() {
compile(
"/** @constructor */",
"function A() {",
"",
" /**",
" * Says hello.",
" * @param {string} name The person to greet.",
" * @return {string} A greeting.",
" * @throws {Error} If the person does not exist.",
" */",
" this.sayHi = function(name) { return 'Hello, ' + name; };",
"}");
NominalType a = typeRegistry.getType("A");
TypeInspector typeInspector = typeInspectorFactory.create(a);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("sayHi")
.setSource(sourceFile("source/foo.js.src.html", 10))
.setDescription(htmlComment("<p>Says hello.</p>\n")))
.addParameter(Detail.newBuilder()
.setName("name")
.setType(stringTypeExpression())
.setDescription(htmlComment("<p>The person to greet.</p>\n")))
.setReturn(Detail.newBuilder()
.setType(stringTypeExpression())
.setDescription(htmlComment("<p>A greeting.</p>\n")))
.addThrown(Detail.newBuilder()
.setType(nullableErrorTypeExpression())
.setDescription(htmlComment("<p>If the person does not exist.</p>\n")))
.build());
}
@Test
public void usesFunctionParameterDataFromJsDoc_noParametersAvailableInSource() {
compile(
DEFINE_ABSTRACT_METHOD,
"/** @constructor */",
"var Clazz = function() {};",
"",
"/**",
" * @param {number} x the first number.",
" * @param {number} y the second number.",
" * @return {number} x + y.",
" */",
"Clazz.prototype.add = abstractMethod");
NominalType type = typeRegistry.getType("Clazz");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("add")
.setSource(sourceFile("source/foo.js.src.html", 10))
.setDescription(Comment.getDefaultInstance()))
.addParameter(Detail.newBuilder()
.setName("x")
.setType(numberTypeExpression())
.setDescription(htmlComment("<p>the first number.</p>\n")))
.addParameter(Detail.newBuilder()
.setName("y")
.setType(numberTypeExpression())
.setDescription(htmlComment("<p>the second number.</p>\n")))
.setReturn(Detail.newBuilder()
.setType(numberTypeExpression())
.setDescription(htmlComment("<p>x + y.</p>\n")))
.build());
}
@Test
@Bug(43)
public void usesFunctionParameterDataFromJsDoc_parameterNamesOnlyNoDescription() {
compile(
DEFINE_ABSTRACT_METHOD,
"/** @constructor */",
"var Clazz = function() {};",
"",
"/**",
" * @param {number} x",
" * @param {number} y",
" */",
"Clazz.prototype.add = abstractMethod");
NominalType type = typeRegistry.getType("Clazz");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("add")
.setSource(sourceFile("source/foo.js.src.html", 9))
.setDescription(Comment.getDefaultInstance()))
.addParameter(Detail.newBuilder()
.setName("x")
.setType(numberTypeExpression()))
.addParameter(Detail.newBuilder()
.setName("y")
.setType(numberTypeExpression()))
.build());
}
@Test
public void pullsFunctionParameterNamesFromSourceIfNoJsDoc() {
compile(
"/** @constructor */",
"var Clazz = function() {};",
"Clazz.prototype.add = function(x, y) { return x + y; };");
NominalType type = typeRegistry.getType("Clazz");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("add")
.setSource(sourceFile("source/foo.js.src.html", 3))
.setDescription(Comment.getDefaultInstance()))
.addParameter(Detail.newBuilder()
.setName("x")
.setType(TypeExpression.newBuilder().setUnknownType(true)))
.addParameter(Detail.newBuilder()
.setName("y")
.setType(TypeExpression.newBuilder().setUnknownType(true)))
.build());
}
@Test
public void jsDocOnlySpecifiesParameterTypesIsSameAsNoDocsAtAll() {
compile(
"/** @constructor */",
"var Clazz = function() {};",
"",
"/**",
" * @param {number}",
" * @param {number}",
" */",
"Clazz.prototype.add = function(x, y) { return x + y; };");
NominalType type = typeRegistry.getType("Clazz");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("add")
.setSource(sourceFile("source/foo.js.src.html", 8))
.setDescription(Comment.getDefaultInstance()))
.addParameter(Detail.newBuilder()
.setName("x")
.setType(TypeExpression.newBuilder().setUnknownType(true)))
.addParameter(Detail.newBuilder()
.setName("y")
.setType(TypeExpression.newBuilder().setUnknownType(true)))
.build());
}
@Test
public void jsDocDoesNotSpecifyParameterTypes() {
compile(
"/** @constructor */",
"var Clazz = function() {};",
"",
"/**",
" * @param x the first number to add.",
" * @param y the second number to add.",
" */",
"Clazz.prototype.add = function(x, y) { return x + y; };");
NominalType type = typeRegistry.getType("Clazz");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("add")
.setSource(sourceFile("source/foo.js.src.html", 8))
.setDescription(Comment.getDefaultInstance()))
.addParameter(Detail.newBuilder()
.setName("x")
.setDescription(htmlComment("<p>the first number to add.</p>\n")))
.addParameter(Detail.newBuilder()
.setName("y")
.setDescription(htmlComment("<p>the second number to add.</p>\n")))
.build());
}
@Test
public void usesPositionalArgsIfNoJsDocAndNoneInSource() {
compile(
DEFINE_ABSTRACT_METHOD,
"/** @constructor */",
"var Clazz = function() {};",
"",
"/** @type {function(number, number)} */",
"Clazz.prototype.add = abstractMethod;");
NominalType type = typeRegistry.getType("Clazz");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("add")
.setSource(sourceFile("source/foo.js.src.html", 6))
.setDescription(Comment.getDefaultInstance()))
.addParameter(Detail.newBuilder()
.setName("arg0")
.setType(numberTypeExpression()))
.addParameter(Detail.newBuilder()
.setName("arg1")
.setType(numberTypeExpression()))
.build());
}
@Test
public void usesOverriddenComment_superClass() {
compile(
DEFINE_ABSTRACT_METHOD,
"/** @constructor */",
"var A = function() {};",
"",
"/** Comment on A. */",
"A.prototype.record = abstractMethod;",
"",
"/** @constructor @extends {A} */",
"var B = function() {};",
"",
"/**",
" * Comment on B.",
" * @override",
" */",
"B.prototype.record = function() {};");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("record")
.setSource(sourceFile("source/foo.js.src.html", 15))
.setDescription(htmlComment("<p>Comment on B.</p>\n"))
.setOverrides(namedType("A", "A.html#record")))
.build());
}
@Test
public void usesOverriddenComment_superInterface() {
compile(
DEFINE_ABSTRACT_METHOD,
"/** @interface */",
"var A = function() {};",
"",
"/** Comment on A. */",
"A.prototype.record = abstractMethod;",
"",
"/** @interface @extends {A} */",
"var B = function() {};",
"",
"/**",
" * Comment on B.",
" * @override",
" */",
"B.prototype.record = function() {};");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("record")
.setSource(sourceFile("source/foo.js.src.html", 15))
.setDescription(htmlComment("<p>Comment on B.</p>\n"))
.addSpecifiedBy(namedType("A", "A.html#record")))
.build());
}
@Test
public void usesOverriddenComment_declaredInterface() {
compile(
DEFINE_ABSTRACT_METHOD,
"/** @interface */",
"var A = function() {};",
"",
"/** Comment on A. */",
"A.prototype.record = abstractMethod;",
"",
"/** @constructor @implements {A} */",
"var B = function() {};",
"",
"/**",
" * Comment on B.",
" * @override",
" */",
"B.prototype.record = function() {};");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("record")
.setSource(sourceFile("source/foo.js.src.html", 15))
.setDescription(htmlComment("<p>Comment on B.</p>\n"))
.addSpecifiedBy(namedType("A", "A.html#record")))
.build());
}
@Test
public void usesCommentFromOverriddenType_superClass() {
compile(
DEFINE_ABSTRACT_METHOD,
"/** @constructor */",
"var A = function() {};",
"",
"/** Comment on A. */",
"A.prototype.record = abstractMethod;",
"",
"/** @constructor @extends {A} */",
"var B = function() {};",
"",
"/**",
" * @override",
" */",
"B.prototype.record = function() {};");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("record")
.setSource(sourceFile("source/foo.js.src.html", 14))
.setDescription(htmlComment("<p>Comment on A.</p>\n"))
.setOverrides(namedType("A", "A.html#record")))
.build());
}
@Test
public void usesCommentFromOverriddenType_superInterface() {
compile(
DEFINE_ABSTRACT_METHOD,
"/** @interface */",
"var A = function() {};",
"",
"/** Comment on A. */",
"A.prototype.record = abstractMethod;",
"",
"/** @interface @extends {A} */",
"var B = function() {};",
"",
"/**",
" * @override",
" */",
"B.prototype.record = function() {};");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("record")
.setSource(sourceFile("source/foo.js.src.html", 14))
.setDescription(htmlComment("<p>Comment on A.</p>\n"))
.addSpecifiedBy(namedType("A", "A.html#record")))
.build());
}
@Test
public void usesParameterInfoFromOverriddenType_superClass() {
compile(
DEFINE_ABSTRACT_METHOD,
"/** @constructor */",
"var A = function() {};",
"",
"/**",
" * @param {number} v The value to record.",
" */",
"A.prototype.record = abstractMethod;",
"",
"/** @constructor @extends {A} */",
"var B = function() {};",
"",
"/** @override */",
"B.prototype.record = function(x) {};");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("record")
.setSource(sourceFile("source/foo.js.src.html", 14))
.setDescription(Comment.getDefaultInstance())
.setOverrides(namedType("A", "A.html#record")))
.addParameter(Detail.newBuilder()
.setName("v")
.setType(numberTypeExpression())
.setDescription(htmlComment("<p>The value to record.</p>\n")))
.build());
}
@Test
public void usesParameterInfoFromOverriddenType_superInterface() {
compile(
DEFINE_ABSTRACT_METHOD,
"/** @interface */",
"var A = function() {};",
"",
"/**",
" * @param {number} v The value to record.",
" */",
"A.prototype.record = abstractMethod;",
"",
"/** @interface @extends {A} */",
"var B = function() {};",
"",
"/** @override */",
"B.prototype.record = function(x) {};");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("record")
.setSource(sourceFile("source/foo.js.src.html", 14))
.setDescription(Comment.getDefaultInstance())
.addSpecifiedBy(namedType("A", "A.html#record")))
.addParameter(Detail.newBuilder()
.setName("v")
.setType(numberTypeExpression())
.setDescription(htmlComment("<p>The value to record.</p>\n")))
.build());
}
@Test
public void usesParameterInfoFromOverriddenType_declaredInterface() {
compile(
DEFINE_ABSTRACT_METHOD,
"/** @interface */",
"var A = function() {};",
"",
"/**",
" * @param {number} v The value to record.",
" */",
"A.prototype.record = abstractMethod;",
"",
"/** @constructor @implements {A} */",
"var B = function() {};",
"",
"/** @override */",
"B.prototype.record = function(x) {};");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("record")
.setSource(sourceFile("source/foo.js.src.html", 14))
.setDescription(Comment.getDefaultInstance())
.addSpecifiedBy(namedType("A", "A.html#record")))
.addParameter(Detail.newBuilder()
.setName("v")
.setType(numberTypeExpression())
.setDescription(htmlComment("<p>The value to record.</p>\n")))
.build());
}
@Test
public void usesParameterInfoFromOverriddenType_interfaceDeclaredOnSuperClass() {
compile(
DEFINE_ABSTRACT_METHOD,
"/** @interface */",
"var A = function() {};",
"",
"/**",
" * @param {number} v The value to record.",
" */",
"A.prototype.record = abstractMethod;",
"",
"/** @constructor @implements {A} */",
"var B = function() {};",
"",
"/** @override */",
"B.prototype.record = function(x) {};",
"",
"/** @constructor @extends {B} */",
"var C = function() {};",
"",
"/** @override */",
"C.prototype.record = function(y) {};");
NominalType type = typeRegistry.getType("C");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("record")
.setSource(sourceFile("source/foo.js.src.html", 20))
.setDescription(Comment.getDefaultInstance())
.setOverrides(namedType("B", "B.html#record"))
.addSpecifiedBy(namedType("A", "A.html#record")))
.addParameter(Detail.newBuilder()
.setName("v")
.setType(numberTypeExpression())
.setDescription(htmlComment("<p>The value to record.</p>\n")))
.build());
}
@Test
public void usesParameterInfoFromOverriddenType_abstractMethodWithSuperClass() {
compile(
DEFINE_ABSTRACT_METHOD,
"/** @constructor */",
"var A = function() {};",
"",
"/**",
" * @param {number} v The value to record.",
" */",
"A.prototype.record = abstractMethod;",
"",
"/** @constructor @extends {A} */",
"var B = function() {};",
"",
"/** @override */",
"B.prototype.record = abstractMethod;");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("record")
.setSource(sourceFile("source/foo.js.src.html", 8))
.setDescription(Comment.getDefaultInstance())
.setDefinedBy(namedType("A", "A.html#record")))
.addParameter(Detail.newBuilder()
.setName("v")
.setType(numberTypeExpression())
.setDescription(htmlComment("<p>The value to record.</p>\n")))
.build());
}
@Test
public void usesParameterInfoFromOverriddenType_abstractMethodWithDeclaredInterface() {
compile(
DEFINE_ABSTRACT_METHOD,
"/** @interface */",
"var A = function() {};",
"",
"/**",
" * @param {number} v The value to record.",
" */",
"A.prototype.record = abstractMethod;",
"",
"/** @constructor @implements {A} */",
"var B = function() {};",
"",
"/** @override */",
"B.prototype.record = abstractMethod;");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("record")
.setSource(sourceFile("source/foo.js.src.html", 14))
.setDescription(Comment.getDefaultInstance())
.addSpecifiedBy(namedType("A", "A.html#record")))
.addParameter(Detail.newBuilder()
.setName("v")
.setType(numberTypeExpression())
.setDescription(htmlComment("<p>The value to record.</p>\n")))
.build());
}
@Test
public void usesReturnInfoFromOverriddenType_superClass() {
compile(
DEFINE_ABSTRACT_METHOD,
"/** @constructor */",
"var A = function() {};",
"",
"/** @return {string} Return from A. */",
"A.prototype.record = abstractMethod;",
"",
"/** @constructor @extends {A} */",
"var B = function() {};",
"",
"/**",
" * @override",
" */",
"B.prototype.record = function() {};");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("record")
.setSource(sourceFile("source/foo.js.src.html", 14))
.setDescription(Comment.getDefaultInstance())
.setOverrides(namedType("A", "A.html#record")))
.setReturn(Detail.newBuilder()
.setType(stringTypeExpression())
.setDescription(htmlComment("<p>Return from A.</p>\n")))
.build());
}
@Test
public void usesReturnInfoFromOverriddenType_superClassEs6() {
compile(
DEFINE_ABSTRACT_METHOD,
"class A {",
" /** @return {string} Return from A. */",
" record() {}",
"}",
"",
"class B extends A {",
" /** @override */",
" record() {}",
"}");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("record")
.setSource(sourceFile("source/foo.js.src.html", 9))
.setDescription(Comment.getDefaultInstance())
.setOverrides(namedType("A", "A.html#record")))
.setReturn(Detail.newBuilder()
.setType(stringTypeExpression())
.setDescription(htmlComment("<p>Return from A.</p>\n")))
.build());
}
@Test
public void usesReturnInfoFromOverriddenType_superInterface() {
compile(
DEFINE_ABSTRACT_METHOD,
"/** @interface */",
"var A = function() {};",
"",
"/** @return {string} Return from A. */",
"A.prototype.record = abstractMethod;",
"",
"/** @interface @extends {A} */",
"var B = function() {};",
"",
"/**",
" * @override",
" */",
"B.prototype.record = function() {};");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("record")
.setSource(sourceFile("source/foo.js.src.html", 14))
.setDescription(Comment.getDefaultInstance())
.addSpecifiedBy(namedType("A", "A.html#record")))
.setReturn(Detail.newBuilder()
.setType(stringTypeExpression())
.setDescription(htmlComment("<p>Return from A.</p>\n")))
.build());
}
@Test
public void usesReturnInfoFromOverriddenType_declaredInterface() {
compile(
DEFINE_ABSTRACT_METHOD,
"/** @interface */",
"var A = function() {};",
"",
"/** @return {string} Return from A. */",
"A.prototype.record = abstractMethod;",
"",
"/** @constructor @implements {A} */",
"var B = function() {};",
"",
"/**",
" * @override",
" */",
"B.prototype.record = function() {};");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("record")
.setSource(sourceFile("source/foo.js.src.html", 14))
.setDescription(Comment.getDefaultInstance())
.addSpecifiedBy(namedType("A", "A.html#record")))
.setReturn(Detail.newBuilder()
.setType(stringTypeExpression())
.setDescription(htmlComment("<p>Return from A.</p>\n")))
.build());
}
@Test
public void usesReturnInfoFromOverriddenType_declaredInterfaceWithNoFunctionBody() {
compile(
DEFINE_ABSTRACT_METHOD,
"",
"/** @interface */",
"var A = function() {};",
"",
"/** @return {string} Return from A. */",
"A.prototype.record;",
"",
"/** @constructor @implements {A} */",
"var B = function() {};",
"",
"/**",
" * @override",
" */",
"B.prototype.record = noOpFunc;");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("record")
.setSource(sourceFile("source/foo.js.src.html", 15))
.setDescription(Comment.getDefaultInstance())
.addSpecifiedBy(namedType("A", "A.html#record")))
.setReturn(Detail.newBuilder()
.setType(stringTypeExpression())
.setDescription(htmlComment("<p>Return from A.</p>\n")))
.build());
}
@Test
public void usesReturnInfoFromOverriddenType_interfaceDeclaredOnSuperClass() {
compile(
DEFINE_ABSTRACT_METHOD,
"/** @interface */",
"var A = function() {};",
"",
"/** @return {string} Return from A. */",
"A.prototype.record = abstractMethod;",
"",
"/** @constructor @implements {A} */",
"var B = function() {};",
"",
"/**",
" * @override",
" */",
"B.prototype.record = function() {};",
"",
"/** @constructor @extends {B} */",
"var C = function() {};",
"",
"/**",
" * @override",
" */",
"C.prototype.record = function() {};");
NominalType type = typeRegistry.getType("C");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("record")
.setSource(sourceFile("source/foo.js.src.html", 22))
.setDescription(Comment.getDefaultInstance())
.setOverrides(namedType("B", "B.html#record"))
.addSpecifiedBy(namedType("A", "A.html#record")))
.setReturn(Detail.newBuilder()
.setType(stringTypeExpression())
.setDescription(htmlComment("<p>Return from A.</p>\n")))
.build());
}
@Test
@Bug(35)
public void usesReturnInfoFromOverriddenType_abstractMethod_interfaceSpecification() {
compile(
DEFINE_ABSTRACT_METHOD,
"/** @interface */",
"var A = function() {};",
"",
"/**",
" * Returns some value.",
" * @return {string} Return from A.",
" */",
"A.prototype.record = function() {};",
"",
"/** @constructor @implements {A} */",
"var B = function() {};",
"",
"/**",
" * @override",
" */",
"B.prototype.record = abstractMethod;");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("record")
.setSource(sourceFile("source/foo.js.src.html", 17))
.setDescription(htmlComment("<p>Returns some value.</p>\n"))
.addSpecifiedBy(namedType("A", "A.html#record")))
.setReturn(Detail.newBuilder()
.setType(stringTypeExpression())
.setDescription(htmlComment("<p>Return from A.</p>\n")))
.build());
}
@Test
@Bug(35)
public void usesReturnInfoFromOverriddenType_abstractMethod_superClass() {
compile(
DEFINE_ABSTRACT_METHOD,
"/** @constructor */",
"var A = function() {};",
"",
"/**",
" * Returns some value.",
" * @return {string} Return from A.",
" */",
"A.prototype.record = function() {};",
"",
"/** @constructor @extends {A} */",
"var B = function() {};",
"",
"/**",
" * @override",
" */",
"B.prototype.record = abstractMethod;");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("record")
.setSource(sourceFile("source/foo.js.src.html", 9))
.setDescription(htmlComment("<p>Returns some value.</p>\n"))
.setDefinedBy(namedType("A", "A.html#record")))
.setReturn(Detail.newBuilder()
.setType(stringTypeExpression())
.setDescription(htmlComment("<p>Return from A.</p>\n")))
.build());
}
@Test
@Bug(35)
public void usesReturnInfoFromOverriddenType_abstractMethod_insideAGoogScope() {
compile(
DEFINE_ABSTRACT_METHOD,
"goog.provide('foo.bar');",
"goog.scope(function() {",
" /**",
" * This is an interface.",
" * @interface",
" */",
" foo.bar.AnInterface = function() {};",
" var A = foo.bar.AnInterface;",
"",
" /**",
" * Returns some value.",
" * @return {string} Return from A.",
" */",
" A.prototype.record = function() {};",
"",
" /**",
" * This is a class.",
" * @constructor",
" * @implements {A}",
" */",
" foo.bar.AClass = function() {};",
" var B = foo.bar.AClass;",
"",
" /**",
" * @override",
" */",
" B.prototype.record = abstractMethod;",
"});");
NominalType type = typeRegistry.getType("foo.bar.AClass");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("record")
.setSource(sourceFile("source/foo.js.src.html", 28))
.setDescription(htmlComment("<p>Returns some value.</p>\n"))
.addSpecifiedBy(namedType(
"foo.bar.AnInterface", "foo.bar.AnInterface.html#record")))
.setReturn(Detail.newBuilder()
.setType(stringTypeExpression())
.setDescription(htmlComment("<p>Return from A.</p>\n")))
.build());
}
@Test
public void usesUnknownReturnTypeForUnqualifiedSubclassOfTemplatizedClass_superClass() {
compile(
"/**",
" * @constructor",
" * @template TYPE",
" */",
"var A = function() {};",
"",
"/** @return {TYPE} The return value. */",
"A.prototype.value = function() {};",
"",
"/** @constructor @extends {A} */",
"var B = function() {};");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("value")
.setSource(sourceFile("source/foo.js.src.html", 8))
.setDescription(Comment.getDefaultInstance())
.setDefinedBy(namedType("A", "A.html#value")))
.setReturn(Detail.newBuilder()
.setDescription(htmlComment("<p>The return value.</p>\n")))
.build());
}
@Test
public void usesUnknownReturnTypeForUnqualifiedSubclassOfTemplatizedClass_superClassEs6() {
compile(
"/**",
" * @template TYPE",
" */",
"class A {",
" /** @return {TYPE} The return value. */",
" value() {}",
"}",
"",
"class B extends A {}");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("value")
.setSource(sourceFile("source/foo.js.src.html", 6))
.setDescription(Comment.getDefaultInstance())
.setDefinedBy(namedType("A", "A.html#value")))
.setReturn(Detail.newBuilder()
.setDescription(htmlComment("<p>The return value.</p>\n")))
.build());
}
@Test
public void usesUnknownReturnTypeForUnqualifiedSubclassOfTemplatizedClass_superInterface() {
compile(
"/**",
" * @interface",
" * @template TYPE",
" */",
"class A {",
" /** @return {TYPE} The return value. */",
" value() {}",
"}",
"",
"class B extends A {}");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("value")
.setSource(sourceFile("source/foo.js.src.html", 7))
.setDescription(Comment.getDefaultInstance()))
.setReturn(Detail.newBuilder()
.setDescription(htmlComment("<p>The return value.</p>\n")))
.build());
}
@Test
public void resolvesReturnTypeForOverriddenTemplateType_superClass() {
compile(
"/**",
" * @constructor",
" * @template TYPE",
" */",
"var A = function() {};",
"",
"/** @return {TYPE} The return value. */",
"A.prototype.value = function() {};",
"",
"/** @constructor @extends {A<string>} */",
"var B = function() {};");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("value")
.setSource(sourceFile("source/foo.js.src.html", 8))
.setDescription(Comment.getDefaultInstance())
.setDefinedBy(namedType("A", "A.html#value")))
.setReturn(Detail.newBuilder()
.setType(stringTypeExpression())
.setDescription(htmlComment("<p>The return value.</p>\n")))
.build());
}
@Test
public void resolvesReturnTypeForOverriddenTemplateType_superClassEs6() {
compile(
"/**",
" * @template TYPE",
" */",
"class A {",
" /** @return {TYPE} The return value. */",
" value() {}",
"}",
"/** @extends {A<string>} */",
"class B extends A {}");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("value")
.setSource(sourceFile("source/foo.js.src.html", 6))
.setDescription(Comment.getDefaultInstance())
.setDefinedBy(namedType("A", "A.html#value")))
.setReturn(Detail.newBuilder()
.setType(stringTypeExpression())
.setDescription(htmlComment("<p>The return value.</p>\n")))
.build());
}
@Test
public void resolvesReturnTypeForOverriddenTemplateType_superInterface() {
compile(
"/**",
" * @interface",
" * @template TYPE",
" */",
"class A {",
" /** @return {TYPE} The return value. */",
" value() {}",
"}",
"/** @extends {A<string>} */",
"class B extends A {}");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("value")
.setSource(sourceFile("source/foo.js.src.html", 7))
.setDescription(Comment.getDefaultInstance()))
.setReturn(Detail.newBuilder()
.setType(stringTypeExpression())
.setDescription(htmlComment("<p>The return value.</p>\n")))
.build());
}
@Test
public void usesRespecifiedReturnTemplateTypeForSubClassOfTemplateType_superClass() {
compile(
"/**",
" * @constructor",
" * @template TYPE",
" */",
"var A = function() {};",
"",
"/** @return {TYPE} The return value. */",
"A.prototype.value = function() {};",
"",
"/** @constructor @extends {A<BTYPE>} @template BTYPE */",
"var B = function() {};");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("value")
.setSource(sourceFile("source/foo.js.src.html", 8))
.setDescription(Comment.getDefaultInstance())
.setDefinedBy(namedType("A", "A.html#value")))
.setReturn(Detail.newBuilder()
.setType(namedTypeExpression("BTYPE"))
.setDescription(htmlComment("<p>The return value.</p>\n")))
.build());
}
@Test
public void usesRespecifiedReturnTemplateTypeForSubClassOfTemplateType_superClassEs6() {
compile(
"/**",
" * @template TYPE",
" */",
"class A {",
" /** @return {TYPE} The return value. */",
" value() {}",
"}",
"/** @extends {A<BTYPE>} @template BTYPE */",
"class B extends A {}");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("value")
.setSource(sourceFile("source/foo.js.src.html", 6))
.setDescription(Comment.getDefaultInstance())
.setDefinedBy(namedType("A", "A.html#value")))
.setReturn(Detail.newBuilder()
.setType(namedTypeExpression("BTYPE"))
.setDescription(htmlComment("<p>The return value.</p>\n")))
.build());
}
@Test
public void usesRespecifiedReturnTemplateTypeForSubClassOfTemplateType_superInterface() {
compile(
"/**",
" * @interface",
" * @template TYPE",
" */",
"class A {",
" /** @return {TYPE} The return value. */",
" value() {}",
"}",
"/** @extends {A<BTYPE>} @template BTYPE */",
"class B extends A {}");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("value")
.setSource(sourceFile("source/foo.js.src.html", 7))
.setDescription(Comment.getDefaultInstance()))
.setReturn(Detail.newBuilder()
.setType(namedTypeExpression("BTYPE"))
.setDescription(htmlComment("<p>The return value.</p>\n")))
.build());
}
@Test
public void canOverrideReturnDescription() {
compile(
DEFINE_ABSTRACT_METHOD,
"/** @interface */",
"var A = function() {};",
"",
"/** @return {string} Return from A. */",
"A.prototype.record = abstractMethod;",
"",
"/** @constructor @implements {A} */",
"var B = function() {};",
"",
"/**",
" * @return Return from B.",
" * @override",
" */",
"B.prototype.record = function() {};");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("record")
.setSource(sourceFile("source/foo.js.src.html", 15))
.setDescription(Comment.getDefaultInstance())
.addSpecifiedBy(namedType("A", "A.html#record")))
.setReturn(Detail.newBuilder()
.setType(stringTypeExpression())
.setDescription(htmlComment("<p>Return from B.</p>\n")))
.build());
}
@Test
public void canNarrowParameterDefinedOnSuperType() {
compile(
DEFINE_ABSTRACT_METHOD,
"/** @constructor */",
"var Person = function() {};",
"",
"/** @constructor @extends {Person} */",
"var Student = function() {};",
"",
"/** @interface */",
"var Greeter = function() {};",
"",
"/**",
" * @param {!Person} person The person to greet.",
" */",
"Greeter.prototype.greet = abstractMethod;",
"",
"/**",
" * @constructor",
" * @implements {Greeter}",
" */",
"var StudentGreeter = function() {};",
"",
"/**",
" * @param {!Student} student The student to greet.",
" * @override",
" */",
"StudentGreeter.prototype.greet = function(student) {};");
NominalType type = typeRegistry.getType("StudentGreeter");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("greet")
.setSource(sourceFile("source/foo.js.src.html", 26))
.setDescription(Comment.getDefaultInstance())
.addSpecifiedBy(namedType("Greeter", "Greeter.html#greet")))
.addParameter(Detail.newBuilder()
.setName("student")
.setType(namedTypeExpression("Student", "Student.html"))
.setDescription(htmlComment("<p>The student to greet.</p>\n")))
.build());
// Sanity check the interface specification.
type = typeRegistry.getType("Greeter");
typeInspector = typeInspectorFactory.create(type);
report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("greet")
.setSource(sourceFile("source/foo.js.src.html", 14))
.setDescription(Comment.getDefaultInstance()))
.addParameter(Detail.newBuilder()
.setName("person")
.setType(namedTypeExpression("Person", "Person.html"))
.setDescription(htmlComment("<p>The person to greet.</p>\n")))
.build());
}
@Test
public void canNarrowReturnTypeOfSuperType() {
compile(
DEFINE_ABSTRACT_METHOD,
"/** @constructor */",
"var Greeting = function() {};",
"",
"/** @constructor @extends {Greeting} */",
"var HappyGreeting = function() {};",
"",
"/** @interface */",
"var Greeter = function() {};",
"",
"/**",
" * @return {!Greeting} Returns a greeting.",
" */",
"Greeter.prototype.greet = abstractMethod;",
"",
"/**",
" * @constructor",
" * @implements {Greeter}",
" */",
"var HappyGreeter = function() {};",
"",
"/**",
" * @return {!HappyGreeting} A happy greeting.",
" * @override",
" */",
"HappyGreeter.prototype.greet = function() {};");
NominalType type = typeRegistry.getType("HappyGreeter");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("greet")
.setSource(sourceFile("source/foo.js.src.html", 26))
.setDescription(Comment.getDefaultInstance())
.addSpecifiedBy(namedType("Greeter", "Greeter.html#greet")))
.setReturn(Detail.newBuilder()
.setType(namedTypeExpression("HappyGreeting", "HappyGreeting.html"))
.setDescription(htmlComment("<p>A happy greeting.</p>\n")))
.build());
// Sanity check the interface specification.
type = typeRegistry.getType("Greeter");
typeInspector = typeInspectorFactory.create(type);
report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("greet")
.setSource(sourceFile("source/foo.js.src.html", 14))
.setDescription(Comment.getDefaultInstance()))
.setReturn(Detail.newBuilder()
.setType(namedTypeExpression("Greeting", "Greeting.html"))
.setDescription(htmlComment("<p>Returns a greeting.</p>\n")))
.build());
}
@Test
public void doesNotInheritDeprecationNoticeFromSuperType() {
compile(
"/** @interface */",
"function A() {}",
"",
"/**",
" * @deprecated Do not use this.",
" */",
"A.prototype.go = function() {};",
"",
"/** @constructor @implements {A} */",
"function B() {}",
"",
"/**",
" * @override",
" */",
"B.prototype.go = function() {};");
NominalType type = typeRegistry.getType("A");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("go")
.setSource(sourceFile("source/foo.js.src.html", 7))
.setDescription(Comment.getDefaultInstance())
.setTags(Tags.newBuilder()
.setIsDeprecated(true))
.setDeprecation(htmlComment("<p>Do not use this.</p>\n")))
.build());
type = typeRegistry.getType("B");
typeInspector = typeInspectorFactory.create(type);
report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("go")
.setSource(sourceFile("source/foo.js.src.html", 15))
.setDescription(Comment.getDefaultInstance())
.addSpecifiedBy(namedType("A", "A.html#go")))
.build());
}
@Test
public void doesNotInheritThrownClausesFromSuperType() {
compile(
"/** @interface */",
"function A() {}",
"",
"/**",
" * @throws {Error} if something goes wrong.",
" */",
"A.prototype.go = function() {};",
"",
"/** @constructor @implements {A} */",
"function B() {}",
"",
"/**",
" * @override",
" */",
"B.prototype.go = function() {};");
NominalType type = typeRegistry.getType("A");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("go")
.setSource(sourceFile("source/foo.js.src.html", 7))
.setDescription(Comment.getDefaultInstance()))
.addThrown(Detail.newBuilder()
.setType(nullableErrorTypeExpression())
.setDescription(htmlComment("<p>if something goes wrong.</p>\n")))
.build());
type = typeRegistry.getType("B");
typeInspector = typeInspectorFactory.create(type);
report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("go")
.setSource(sourceFile("source/foo.js.src.html", 15))
.setDescription(Comment.getDefaultInstance())
.addSpecifiedBy(namedType("A", "A.html#go")))
.build());
}
@Test
public void includesFunctionsDefinedBySuperTypeButNotOverriddenByInspectedType() {
compile(
"/** @constructor */",
"function A() {}",
"",
"/**",
" * Runs this instance.",
" */",
"A.prototype.run = function() {};",
"",
"/** @constructor @extends {A} */",
"function B() {}");
NominalType type = typeRegistry.getType("B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("run")
.setSource(sourceFile("source/foo.js.src.html", 7))
.setDescription(htmlComment("<p>Runs this instance.</p>\n"))
.setDefinedBy(namedType("A", "A.html#run")))
.build());
}
@Test
@Bug(30)
public void abstractMethodInheritsDocsFromInterfaceSpecification() {
compile(
DEFINE_ABSTRACT_METHOD,
"/**",
" * @interface",
" */",
"var Edge = function() {};",
"",
"/**",
" * Render this edge to the given context",
" * @param {!CanvasRenderingContext2D} context The canvas to draw this object into",
" */",
"Edge.prototype.addPath = abstractMethod;",
"",
"/**",
" * Abstract edge implementation.",
" * @constructor",
" * @struct",
" * @implements {Edge}",
" */",
"var AbstractEdge = function() {};",
"",
"/** @inheritDoc */",
"AbstractEdge.prototype.addPath = function() {};");
NominalType type = typeRegistry.getType("AbstractEdge");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("addPath")
.setSource(sourceFile("source/foo.js.src.html", 22))
.setDescription(htmlComment("<p>Render this edge to the given context</p>\n"))
.addSpecifiedBy(namedType("Edge", "Edge.html#addPath")))
.addParameter(Detail.newBuilder()
.setName("context")
.setType(namedTypeExpression("CanvasRenderingContext2D"))
.setDescription(htmlComment("<p>The canvas to draw this object into</p>\n")))
.build());
}
@Test
@Bug(38)
public void methodsCanLinkToOtherMethodsOnTheSameClass() {
compile(
"/** @constructor */",
"var Foo = function() {};",
"",
"/** Go to {@link #b}. */",
"Foo.prototype.a = function() {};",
"",
"/** Go to {@link #a}. */",
"Foo.prototype.b = function() {};");
NominalType type = typeRegistry.getType("Foo");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("a")
.setSource(sourceFile("source/foo.js.src.html", 5))
.setDescription(htmlComment(
"<p>Go to <a href=\"Foo.html#b\"><code>#b</code></a>.</p>\n")))
.build(),
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("b")
.setSource(sourceFile("source/foo.js.src.html", 8))
.setDescription(htmlComment(
"<p>Go to <a href=\"Foo.html#a\"><code>#a</code></a>.</p>\n")))
.build());
}
@Test
@Bug(38)
public void methodsCanLinkToMethodDefinedBySuperClass() {
compile(
"/** @constructor */",
"var Foo = function() {};",
"Foo.prototype.a = function() {};",
"",
"/** @constructor @extends {Foo} */",
"var Bar = function() {};",
"",
"/** Go to {@link #a}. */",
"Bar.prototype.b = function() {};");
NominalType type = typeRegistry.getType("Bar");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("a")
.setSource(sourceFile("source/foo.js.src.html", 3))
.setDescription(Comment.getDefaultInstance())
.setDefinedBy(namedType("Foo", "Foo.html#a")))
.build(),
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("b")
.setSource(sourceFile("source/foo.js.src.html", 9))
.setDescription(htmlComment(
"<p>Go to <a href=\"Bar.html#a\"><code>#a</code></a>.</p>\n")))
.build());
}
@Test
@Bug(38)
public void methodLinksToOverriddenMethodIfNoTypeQualifierSpecified() {
compile(
"/** @constructor */",
"var Foo = function() {};",
"Foo.prototype.a = function() {};",
"",
"/** @constructor @extends {Foo} */",
"var Bar = function() {};",
"",
"/** @override */",
"Bar.prototype.a = function() {};",
"",
"/** Go to {@link #a}. */",
"Bar.prototype.b = function() {};");
NominalType type = typeRegistry.getType("Bar");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("a")
.setSource(sourceFile("source/foo.js.src.html", 9))
.setDescription(Comment.getDefaultInstance())
.setOverrides(namedType("Foo", "Foo.html#a")))
.build(),
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("b")
.setSource(sourceFile("source/foo.js.src.html", 12))
.setDescription(htmlComment(
"<p>Go to <a href=\"Bar.html#a\"><code>#a</code></a>.</p>\n")))
.build());
}
@Test
@Bug(38)
public void methodCanLinkToOriginalDefinitionIfQualifierProvided() {
compile(
"/** @constructor */",
"var Foo = function() {};",
"Foo.prototype.a = function() {};",
"",
"/** @constructor @extends {Foo} */",
"var Bar = function() {};",
"",
"/** @override */",
"Bar.prototype.a = function() {};",
"",
"/** Go to {@link Foo#a}. */",
"Bar.prototype.b = function() {};");
NominalType type = typeRegistry.getType("Bar");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("a")
.setSource(sourceFile("source/foo.js.src.html", 9))
.setDescription(Comment.getDefaultInstance())
.setOverrides(namedType("Foo", "Foo.html#a")))
.build(),
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("b")
.setSource(sourceFile("source/foo.js.src.html", 12))
.setDescription(htmlComment(
"<p>Go to <a href=\"Foo.html#a\"><code>Foo#a</code></a>.</p>\n")))
.build());
}
@Test
@Bug(38)
public void methodCanLinkToMethodOnAnotherClass() {
compile(
"/** @constructor */",
"var Foo = function() {};",
"Foo.prototype.a = function() {};",
"",
"/** @constructor */",
"var Bar = function() {};",
"/** Go to {@link Foo#a}. */",
"Bar.prototype.b = function() {};");
NominalType type = typeRegistry.getType("Bar");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("b")
.setSource(sourceFile("source/foo.js.src.html", 8))
.setDescription(htmlComment(
"<p>Go to <a href=\"Foo.html#a\"><code>Foo#a</code></a>.</p>\n")))
.build());
}
@Test
public void methodSpecifiedByInterfaceInAnotherModule() {
util.compile(
createSourceFile(
fs.getPath("/src/modules/one.js"),
"",
"/** @interface */",
"export class Greeter {",
" /** Returns a greeting. */",
" greet() {}",
"}"),
createSourceFile(
fs.getPath("/src/modules/two.js"),
"",
"/** @implements {./one.Greeter} */",
"export class CustomGreeter {",
" /** @override */",
" greet() {}",
"}"));
NominalType type = typeRegistry.getType("module$$src$modules$two.CustomGreeter");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("greet")
.setSource(sourceFile("../source/modules/two.js.src.html", 5))
.setDescription(htmlComment("<p>Returns a greeting.</p>\n"))
.addSpecifiedBy(
namedType("Greeter", "one.Greeter", "one_exports_Greeter.html#greet")))
.build());
}
@Test
public void moduleClassInheritsMethodsFromAnotherModuleClass() {
util.compile(
createSourceFile(
fs.getPath("/src/modules/a/b/c.js"),
"class X {}",
"export { X as Y }",
"",
"export class Z {",
" /**",
" * @param {!X} x A reference to {@link X}",
" */",
" method(x) {}",
"}"),
createSourceFile(
fs.getPath("/src/modules/one.js"),
"",
"import {Z} from './a/b/c';",
"export class B extends Z {}"));
NominalType type = typeRegistry.getType("module$$src$modules$one.B");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("method")
.setSource(sourceFile("../source/modules/a/b/c.js.src.html", 8))
.setDescription(Comment.getDefaultInstance())
.setDefinedBy(namedType("Z", "a/b/c.Z", "a/b/c_exports_Z.html#method")))
.addParameter(Detail.newBuilder()
.setName("x")
.setType(namedTypeExpression("Y", "a/b/c.Y", "a/b/c_exports_Y.html"))
.setDescription(htmlComment(
"<p>A reference to <a href=\"a/b/c_exports_Y.html\">" +
"<code>X</code></a></p>\n")))
.build());
}
@Test
public void overriddenMethodsInheritVisibilityFromParentType() {
util.compile(
createSourceFile(
fs.getPath("/src/globals.js"),
"class Greeter {",
" /** @protected */greet() {}",
"}",
"",
"class CustomGreeter extends Greeter {",
" /** @override */greet() {}",
"}",
"",
"class FinalGreeter extends CustomGreeter {",
" /** @override */greet() {}",
"}"));
NominalType type = typeRegistry.getType("Greeter");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectInstanceType();
assertThat(report.getCompilerConstants()).isEmpty();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("greet")
.setSource(sourceFile("source/globals.js.src.html", 2))
.setDescription(Comment.getDefaultInstance())
.setVisibility(Visibility.newBuilder().setProtected(true)))
.build());
type = typeRegistry.getType("CustomGreeter");
typeInspector = typeInspectorFactory.create(type);
report = typeInspector.inspectInstanceType();
assertThat(report.getCompilerConstants()).isEmpty();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("greet")
.setSource(sourceFile("source/globals.js.src.html", 6))
.setDescription(Comment.getDefaultInstance())
.setOverrides(namedType("Greeter", "Greeter.html#greet"))
.setVisibility(Visibility.newBuilder().setProtected(true)))
.build());
type = typeRegistry.getType("FinalGreeter");
typeInspector = typeInspectorFactory.create(type);
report = typeInspector.inspectInstanceType();
assertThat(report.getCompilerConstants()).isEmpty();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("greet")
.setSource(sourceFile("source/globals.js.src.html", 10))
.setDescription(Comment.getDefaultInstance())
.setOverrides(namedType("CustomGreeter", "CustomGreeter.html#greet"))
.setVisibility(Visibility.newBuilder().setProtected(true)))
.build());
}
@Test
public void methodReturnsTemplatizedType() {
compile(
"/** @template T */",
"class Container {}",
"",
"/** @interface */",
"class Company {",
" /** @return {!Container<string>} . */",
" getEmployees() {}",
"}");
NominalType type = typeRegistry.getType("Company");
TypeInspector inspector = typeInspectorFactory.create(type);
TypeInspector.Report report = inspector.inspectInstanceType();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(
BaseProperty.newBuilder()
.setName("getEmployees")
.setSource(sourceFile("source/foo.js.src.html", 7))
.setDescription(Comment.getDefaultInstance()))
.setReturn(
Detail.newBuilder()
.setType(
TypeExpression.newBuilder()
.setNamedType(
addTemplateTypes(
namedType("Container", "Container.html"),
stringTypeExpression())))
.setDescription(htmlComment("<p>.</p>\n")))
.build());
}
@Test
public void methodReturnsTemplatizedType_es6Modules() {
util.compile(
createSourceFile(
fs.getPath("/src/modules/container.js"),
"/** @template T */",
"export class Container {}"),
createSourceFile(
fs.getPath("/src/modules/company.js"),
"import {Container} from './container';",
"",
"/** @interface */",
"export class Company {",
" /** @return {!Container<string>} . */",
" getEmployees() {}",
"}"));
NominalType type = typeRegistry.getType("module$$src$modules$company.Company");
TypeInspector inspector = typeInspectorFactory.create(type);
TypeInspector.Report report = inspector.inspectInstanceType();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(
BaseProperty.newBuilder()
.setName("getEmployees")
.setSource(sourceFile("../source/modules/company.js.src.html", 6))
.setDescription(Comment.getDefaultInstance()))
.setReturn(
Detail.newBuilder()
.setType(
TypeExpression.newBuilder()
.setNamedType(
addTemplateTypes(
namedType("Container", "container.Container",
"container_exports_Container.html"),
stringTypeExpression())))
.setDescription(htmlComment("<p>.</p>\n")))
.build());
}
@Test
public void methodReturnsTemplatizedType_nodeModules() {
util.compile(
createSourceFile(
fs.getPath("/src/modules/foo/bar.js"),
"/** @template T */",
"class Container {}",
"module.exports = {Container};"),
createSourceFile(
fs.getPath("/src/modules/foo/baz.js"),
"var bar = require('./bar');",
"",
"/** @interface */",
"class Company {",
" /** @return {!bar.Container<string>} . */",
" getEmployees() {}",
"}",
"module.exports = {Company};"));
NominalType type = typeRegistry.getType("module$exports$module$$src$modules$foo$baz.Company");
TypeInspector inspector = typeInspectorFactory.create(type);
TypeInspector.Report report = inspector.inspectInstanceType();
assertThat(report.getFunctions()).containsExactly(
Function.newBuilder()
.setBase(
BaseProperty.newBuilder()
.setName("getEmployees")
.setSource(sourceFile("../../source/modules/foo/baz.js.src.html", 6))
.setDescription(Comment.getDefaultInstance()))
.setReturn(
Detail.newBuilder()
.setType(
TypeExpression.newBuilder()
.setNamedType(
addTemplateTypes(
namedType("Container", "foo/bar.Container",
"bar_exports_Container.html"),
stringTypeExpression())))
.setDescription(htmlComment("<p>.</p>\n")))
.build());
}
@Test
public void handlesParameterTypesDefinedFromInsideAnotherModule_closure() {
util.compile(
createSourceFile(fs.getPath("/src/modules/foo.js"),
"goog.module('foo');",
"class Foo {}",
"exports = {Foo}"),
createSourceFile(fs.getPath("/src/modules/bar.js"),
"goog.module('bar');",
"var foo = goog.require('foo');",
"class Bar {",
" /** @param {!foo.Foo} input */",
" work(input) {}",
"}",
"exports = {Bar}"),
createSourceFile(fs.getPath("/src/modules/baz.js"),
"goog.module('baz');",
"var bar = goog.require('bar');",
"class Other {}",
"exports = {Baz: bar.Bar, Other}"));
NominalType type = typeRegistry.getType("module$exports$baz.Baz");
TypeInspector inspector = typeInspectorFactory.create(type);
TypeInspector.Report report = inspector.inspectInstanceType();
Function function = getOnlyElement(report.getFunctions());
assertThat(function.getBase().getName()).isEqualTo("work");
assertThat(function.getParameterList())
.containsExactly(
Detail.newBuilder()
.setName("input")
.setType(namedTypeExpression("Foo", "foo.Foo", "foo.Foo.html"))
.build());
}
@Test
public void handlesParameterTypesDefinedFromInsideAnotherModule_node() {
guice.toBuilder()
.setModules("/src/modules/foo.js", "/src/modules/bar.js", "/src/modules/baz.js")
.build()
.createInjector()
.injectMembers(this);
util.compile(
createSourceFile(fs.getPath("/src/modules/foo.js"),
"class Foo {}",
"module.exports = {Foo}"),
createSourceFile(fs.getPath("/src/modules/bar.js"),
"var Foo = require('./foo').Foo;",
"class Bar {",
" /** @param {!Foo} input */",
" constructor(input) {}",
"",
" /** @param {!Foo} input */",
" work(input) {}",
"}",
"module.exports = {Bar}"),
createSourceFile(fs.getPath("/src/modules/baz.js"),
"var bar = require('./bar');",
"exports.Baz = bar.Bar;"));
NominalType type = typeRegistry.getType("module$exports$module$$src$modules$baz.Baz");
TypeInspector inspector = typeInspectorFactory.create(type);
Node fakeNode = fakeNodeForType(type);
FunctionType mainFn = checkNotNull(type.getType().toMaybeFunctionType(),
"Expected %s to be a function: %s", type.getName(), type.getType());
TypeInspector.Report report = inspector.inspectInstanceType();
Function function = getOnlyElement(report.getFunctions());
// Check an instance method, which should work as it's just a copied alias.
assertThat(function.getBase().getName()).isEqualTo("work");
assertThat(function.getParameterList())
.containsExactly(
Detail.newBuilder()
.setName("input")
.setType(namedTypeExpression("Foo", "foo.Foo", "foo_exports_Foo.html"))
.build());
// Check the constructor too.
function = inspector.getFunctionData("constructor", mainFn, fakeNode, type, type.getJsDoc());
assertThat(function.getParameterList())
.containsExactly(
Detail.newBuilder()
.setName("input")
.setType(namedTypeExpression("Foo", "foo.Foo", "foo_exports_Foo.html"))
.build());
}
}