/*
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.testing.CompilerUtil.createSourceFile;
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.Property;
import com.github.jsdossier.proto.Tags;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for extracting property information with a {@link TypeInspector}.
*/
@RunWith(JUnit4.class)
public class TypeInspectorStaticPropertyTest extends AbstractTypeInspectorTest {
@Test
public void doesNotReturnNestedTypeAsProperty() {
compile(
"/** @constructor */",
"function A() {}",
"",
"A.B = class {};",
"",
"/** @constructor */",
"A.C = function() {};",
"",
"/** @interface */",
"A.D = function() {};",
"",
"/** @enum {string} */",
"A.E = {};");
NominalType a = typeRegistry.getType("A");
TypeInspector typeInspector = typeInspectorFactory.create(a);
TypeInspector.Report report = typeInspector.inspectType();
assertThat(report.getFunctions()).isEmpty();
assertThat(report.getCompilerConstants()).isEmpty();
assertThat(report.getProperties()).isEmpty();
}
@Test
public void doesNotReturnNestedNamespacesAsProperty() {
compile(
"goog.provide('foo');",
"goog.provide('foo.bar');",
"",
"foo.x = 123;",
"foo.bar.y = 456;");
NominalType type = typeRegistry.getType("foo");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectType();
assertThat(report.getFunctions()).isEmpty();
assertThat(report.getCompilerConstants()).isEmpty();
assertThat(report.getProperties()).containsExactly(
Property.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("x")
.setSource(sourceFile("source/foo.js.src.html", 4))
.setDescription(Comment.getDefaultInstance()))
.setType(numberTypeExpression())
.build());
}
@Test
public void doesNotReturnNestedNamespacesAsProperty_subNamespaceHasBeenFiltered() {
guice.toBuilder()
.setTypeNameFilter("foo.bar"::equals)
.build()
.createInjector()
.injectMembers(this);
compile(
"goog.provide('foo');",
"goog.provide('foo.bar');",
"",
"foo.x = 123;",
"foo.bar.y = 456;");
assertThat(typeRegistry.isType("foo.bar")).isFalse();
NominalType type = typeRegistry.getType("foo");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectType();
assertThat(report.getFunctions()).isEmpty();
assertThat(report.getCompilerConstants()).isEmpty();
assertThat(report.getProperties()).containsExactly(
Property.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("x")
.setSource(sourceFile("source/foo.js.src.html", 4))
.setDescription(Comment.getDefaultInstance()))
.setType(numberTypeExpression())
.build());
}
@Test
public void returnsInfoOnStaticProperties_constructor() {
compile(
"/** @constructor */",
"function A() {}",
"",
"/**",
" * A property.",
" * @type {number}",
" */",
"A.b = 1234;");
NominalType a = typeRegistry.getType("A");
TypeInspector typeInspector = typeInspectorFactory.create(a);
TypeInspector.Report report = typeInspector.inspectType();
assertThat(report.getFunctions()).isEmpty();
assertThat(report.getCompilerConstants()).isEmpty();
assertThat(report.getProperties()).containsExactly(
Property.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("A.b")
.setSource(sourceFile("source/foo.js.src.html", 8))
.setDescription(htmlComment("<p>A property.</p>\n")))
.setType(numberTypeExpression())
.build());
}
@Test
public void returnsInfoOnStaticProperties_interface() {
compile(
"/** @interface */",
"function A() {}",
"",
"/**",
" * A property.",
" * @type {number}",
" */",
"A.b = 1234;");
NominalType a = typeRegistry.getType("A");
TypeInspector typeInspector = typeInspectorFactory.create(a);
TypeInspector.Report report = typeInspector.inspectType();
assertThat(report.getFunctions()).isEmpty();
assertThat(report.getCompilerConstants()).isEmpty();
assertThat(report.getProperties()).containsExactly(
Property.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("A.b")
.setSource(sourceFile("source/foo.js.src.html", 8))
.setDescription(htmlComment("<p>A property.</p>\n")))
.setType(numberTypeExpression())
.build());
}
@Test
public void returnsInfoOnStaticProperties_namespace() {
compile(
"goog.provide('A');",
"",
"/**",
" * A property.",
" * @type {number}",
" */",
"A.b = 1234;");
NominalType a = typeRegistry.getType("A");
TypeInspector typeInspector = typeInspectorFactory.create(a);
TypeInspector.Report report = typeInspector.inspectType();
assertThat(report.getFunctions()).isEmpty();
assertThat(report.getCompilerConstants()).isEmpty();
assertThat(report.getProperties()).containsExactly(
Property.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("b")
.setSource(sourceFile("source/foo.js.src.html", 7))
.setDescription(htmlComment("<p>A property.</p>\n")))
.setType(numberTypeExpression())
.build());
}
@Test
public void returnsInfoOnCompilerConstants() {
compile(
"goog.provide('foo.bar');",
"",
"/**",
" * This is a constant defined on foo.bar.",
" * @define {number}",
" */",
"foo.bar.BAZ = 1234;");
NominalType a = typeRegistry.getType("foo.bar");
TypeInspector typeInspector = typeInspectorFactory.create(a);
TypeInspector.Report report = typeInspector.inspectType();
assertThat(report.getFunctions()).isEmpty();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getCompilerConstants()).containsExactly(
Property.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("foo.bar.BAZ")
.setSource(sourceFile("source/foo.js.src.html", 7))
.setDescription(htmlComment("<p>This is a constant defined on foo.bar.</p>\n"))
.setTags(Tags.newBuilder()
.setIsConst(true)))
.setType(numberTypeExpression())
.build());
}
@Test
public void returnsInfoOnCompilerConstants_usingGoogDefine() {
compile(
"goog.provide('foo.bar');",
"",
"/**",
" * This is a constant defined on foo.bar.",
" * @define {number}",
" */",
"goog.define('foo.bar.BAZ', 1234);");
NominalType a = typeRegistry.getType("foo.bar");
TypeInspector typeInspector = typeInspectorFactory.create(a);
TypeInspector.Report report = typeInspector.inspectType();
assertThat(report.getFunctions()).isEmpty();
assertThat(report.getProperties()).isEmpty();
assertThat(report.getCompilerConstants()).containsExactly(
Property.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("foo.bar.BAZ")
.setSource(sourceFile("source/foo.js.src.html", 7))
.setDescription(htmlComment("<p>This is a constant defined on foo.bar.</p>\n"))
.setTags(Tags.newBuilder()
.setIsConst(true)))
.setType(numberTypeExpression())
.build());
}
@Test
public void linkReferencesAreParsedRelativeToOwningType() {
util.compile(
createSourceFile(
fs.getPath("/src/globals.js"),
"/** Global person. */",
"class Person {}"),
createSourceFile(
fs.getPath("/src/modules/foo/bar.js"),
"",
"/** Hides global person. */",
"class Person {}",
"exports.Person = Person;",
"",
"/** Link to a {@link Person}. */",
"exports.limit = 123;"));
NominalType type = typeRegistry.getType("module$exports$module$$src$modules$foo$bar");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectType();
assertThat(report.getCompilerConstants()).isEmpty();
assertThat(report.getProperties()).containsExactly(
Property.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("limit")
.setSource(sourceFile("../../source/modules/foo/bar.js.src.html", 7))
.setDescription(htmlComment(
"<p>Link to a <a href=\"bar_exports_Person.html\">" +
"<code>Person</code></a>.</p>\n"))
.setTags(Tags.newBuilder()
.setIsConst(true)))
.setType(numberTypeExpression())
.build());
}
@Test
public void identifiesDefaultModuleExport() {
util.compile(
fs.getPath("/src/modules/foo/baz.js"),
"/** Hello, world! */",
"const x = 1234;",
"export default x;");
NominalType type = typeRegistry.getType("module$$src$modules$foo$baz");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectType();
assertThat(report.getProperties()).containsExactly(
Property.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("default")
.setSource(sourceFile("../../source/modules/foo/baz.js.src.html", 3))
.setDescription(htmlComment("<p>Hello, world!</p>\n"))
.setTags(Tags.newBuilder().setIsDefault(true))
.build())
.setType(numberTypeExpression())
.build());
}
@Test
public void moduleExportsAnotherModuleAsAProperty() {
util.compile(
createSourceFile(
fs.getPath("/src/modules/foo/bar.js"),
"exports.One = class {};"),
createSourceFile(
fs.getPath("/src/modules/foo/baz.js"),
"let bar = require('./bar');",
"exports.bar = bar;"));
NominalType type = typeRegistry.getType("module$exports$module$$src$modules$foo$baz");
TypeInspector typeInspector = typeInspectorFactory.create(type);
TypeInspector.Report report = typeInspector.inspectType();
assertThat(report.getProperties()).containsExactly(
Property.newBuilder()
.setBase(BaseProperty.newBuilder()
.setName("bar")
.setSource(sourceFile("../../source/modules/foo/baz.js.src.html", 2))
.setDescription(Comment.getDefaultInstance())
.setTags(Tags.newBuilder().setIsModule(true))
.build())
.setType(namedTypeExpression("foo/bar", "bar.html"))
.build());
}
}