/*
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.extensions.proto.ProtoTruth.assertThat;
import com.github.jsdossier.jscomp.NominalType;
import com.github.jsdossier.proto.NamedType;
import com.github.jsdossier.proto.TypeExpression;
import com.google.common.truth.extensions.proto.IterableOfProtosSubject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests inheritance processing for the {@link TypeInspector}.
*/
@RunWith(JUnit4.class)
public class TypeInspectorInheritanceTest extends AbstractTypeInspectorTest {
private static final NamedType TYPE_A = namedType("A", "A.html");
private static final NamedType TYPE_B = namedType("B", "B.html");
private static final NamedType TYPE_C = namedType("C", "C.html");
private static final NamedType TYPE_D = namedType("D", "D.html");
private static final NamedType TYPE_E = namedType("E", "E.html");
@Test
public void interfaceThatExtendsOneInterface() {
compile(
"/** @interface */",
"function A() {}",
"",
"/**",
" * @interface",
" * @extends {A}",
" */",
"function B() {}");
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
assertImplementedTypes(a).isEmpty();
assertKnownImplementations(a).isEmpty();
assertSubtypes(a).containsExactly(TYPE_B);
assertImplementedTypes(b).containsExactly(TYPE_A);
assertKnownImplementations(b).isEmpty();
assertSubtypes(b).isEmpty();
}
@Test
public void interfaceThatExtendsOneInterface_es6() {
compile(
"/** @interface */ class A {}",
"/** @interface */ class B extends A {}");
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
assertImplementedTypes(a).isEmpty();
assertKnownImplementations(a).isEmpty();
assertSubtypes(a).containsExactly(TYPE_B);
assertImplementedTypes(b).containsExactly(TYPE_A);
assertKnownImplementations(b).isEmpty();
assertSubtypes(b).isEmpty();
}
@Test
public void interfaceThatExtendsMultipleInterfaces() {
compile(
"/** @interface */ function A() {}",
"/** @interface */ function B() {}",
"",
"/**",
" * @interface",
" * @extends {A}",
" * @extends {B}",
" */",
"function C() {}");
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
NominalType c = typeRegistry.getType("C");
assertImplementedTypes(a).isEmpty();
assertKnownImplementations(a).isEmpty();
assertSubtypes(a).containsExactly(TYPE_C);
assertImplementedTypes(b).isEmpty();
assertKnownImplementations(b).isEmpty();
assertSubtypes(b).containsExactly(TYPE_C);
assertImplementedTypes(c).containsExactly(TYPE_A, TYPE_B);
assertKnownImplementations(c).isEmpty();
assertSubtypes(c).isEmpty();
}
@Test
public void multipleLevelsOfInterfaceExtension_es5() {
compile(
"/** @interface */ function A() {}",
"/** @interface @extends {A} */ function B() {}",
"/** @interface @extends {B} */ function C() {}",
"/** @interface @extends {C} */ function D() {}");
checkMultipleLevelsOfInterfaceExtension();
}
@Test
public void multipleLevelsOfInterfaceExtension_es6() {
compile(
"/** @interface */ class A {}",
"/** @interface */ class B extends A {}",
"/** @interface */ class C extends B {}",
"/** @interface */ class D extends C {}");
checkMultipleLevelsOfInterfaceExtension();
}
private void checkMultipleLevelsOfInterfaceExtension() {
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
NominalType c = typeRegistry.getType("C");
NominalType d = typeRegistry.getType("D");
assertImplementedTypes(a).isEmpty();
assertKnownImplementations(a).isEmpty();
assertSubtypes(a).containsExactly(TYPE_B, TYPE_C, TYPE_D);
assertImplementedTypes(b).containsExactly(TYPE_A);
assertKnownImplementations(b).isEmpty();
assertSubtypes(b).containsExactly(TYPE_C, TYPE_D);
assertImplementedTypes(c).containsExactly(TYPE_A, TYPE_B);
assertKnownImplementations(c).isEmpty();
assertSubtypes(c).containsExactly(TYPE_D);
assertImplementedTypes(d).containsExactly(TYPE_A, TYPE_B, TYPE_C);
assertKnownImplementations(d).isEmpty();
assertSubtypes(d).isEmpty();
}
@Test
public void multipleBranchesOfInterfaceExtension() {
compile(
"/** @interface */ class A {}",
"/** @interface */ class B extends A {}",
"/** @interface */ class C extends A {}",
"/** @interface @extends {B} */ class D extends C {}");
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
NominalType c = typeRegistry.getType("C");
NominalType d = typeRegistry.getType("D");
assertImplementedTypes(a).isEmpty();
assertKnownImplementations(a).isEmpty();
assertSubtypes(a).containsExactly(TYPE_B, TYPE_C, TYPE_D);
assertImplementedTypes(b).containsExactly(TYPE_A);
assertKnownImplementations(b).isEmpty();
assertSubtypes(b).containsExactly(TYPE_D);
assertImplementedTypes(c).containsExactly(TYPE_A);
assertKnownImplementations(c).isEmpty();
assertSubtypes(c).containsExactly(TYPE_D);
assertImplementedTypes(d).containsExactly(TYPE_A, TYPE_B, TYPE_C);
assertKnownImplementations(d).isEmpty();
assertSubtypes(d).isEmpty();
}
@Test
public void classImplementsOneInterface() {
compile(
"/** @interface */ class A {}",
"/** @implements {A} */ class B {}");
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
assertImplementedTypes(a).isEmpty();
assertKnownImplementations(a).containsExactly(TYPE_B);
assertSubtypes(a).isEmpty();
assertImplementedTypes(b).containsExactly(TYPE_A);
assertKnownImplementations(b).isEmpty();
assertSubtypes(b).isEmpty();
}
@Test
public void templateTypeImplementsTemplateInterface() {
compile(
"/**",
" * @template T",
" * @interface",
" */",
"function A() {}",
"",
"/**",
" * @constructor",
" * @template TYPE",
" * @implements {A<TYPE>}",
" */",
"function B() {}");
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
assertImplementedTypes(a).isEmpty();
assertSubtypes(a).isEmpty();
assertKnownImplementations(a).containsExactly(TYPE_B);
assertKnownImplementations(b).isEmpty();
assertSubtypes(b).isEmpty();
assertImplementedTypes(b)
.containsExactly(addTemplateTypes(TYPE_A, namedTypeExpression("TYPE")));
}
@Test
public void typeImplementsTemplatizedType() {
compile(
"/**",
" * @template T",
" * @interface",
" */",
"function A() {}",
"",
"/**",
" * @constructor",
" * @template TYPE",
" * @implements {A<string>}",
" */",
"function B() {}");
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
assertImplementedTypes(a).isEmpty();
assertSubtypes(a).isEmpty();
assertKnownImplementations(a).containsExactly(TYPE_B);
assertKnownImplementations(b).isEmpty();
assertSubtypes(b).isEmpty();
assertImplementedTypes(b)
.containsExactly(addTemplateTypes(TYPE_A, stringTypeExpression()));
}
@Test
public void implementedTypesForAnInterfaceDoesNotIncludeTheInterfaceItself() {
compile(
"/**",
" * @interface",
" */",
"function A() {}");
NominalType a = typeRegistry.getType("A");
assertImplementedTypes(a).isEmpty();
assertSubtypes(a).isEmpty();
assertKnownImplementations(a).isEmpty();
}
@Test
public void implementsMultipleInterfaces() {
compile(
"/** @interface */ function A() {}",
"/** @interface */ function B() {}",
"",
"/**",
" * @constructor",
" * @implements {A}",
" * @implements {B}",
" */",
"function C() {}");
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
NominalType c = typeRegistry.getType("C");
assertImplementedTypes(a).isEmpty();
assertKnownImplementations(a).containsExactly(TYPE_C);
assertSubtypes(a).isEmpty();
assertImplementedTypes(b).isEmpty();
assertKnownImplementations(b).containsExactly(TYPE_C);
assertSubtypes(b).isEmpty();
assertImplementedTypes(c).containsExactly(TYPE_A, TYPE_B);
assertKnownImplementations(c).isEmpty();
assertSubtypes(c).isEmpty();
}
@Test
public void templateTypeImplementsMultipleTemplateInterfaces() {
compile(
"/**",
" * @template A_TYPE",
" * @interface",
" */",
"function A() {}",
"",
"/**",
" * @template B_TYPE",
" * @interface",
" */",
"function B() {}",
"",
"/**",
" * @constructor",
" * @template SPECIFIC_TYPE",
" * @implements {A<SPECIFIC_TYPE>}",
" * @implements {B<SPECIFIC_TYPE>}",
" */",
"function C() {}");
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
NominalType c = typeRegistry.getType("C");
assertImplementedTypes(a).isEmpty();
assertKnownImplementations(a).containsExactly(TYPE_C);
assertSubtypes(a).isEmpty();
assertImplementedTypes(b).isEmpty();
assertKnownImplementations(b).containsExactly(TYPE_C);
assertSubtypes(b).isEmpty();
assertImplementedTypes(c)
.containsExactly(
addTemplateTypes(TYPE_A, namedTypeExpression("SPECIFIC_TYPE")),
addTemplateTypes(TYPE_B, namedTypeExpression("SPECIFIC_TYPE")));
assertKnownImplementations(c).isEmpty();
assertSubtypes(c).isEmpty();
}
@Test
public void typeImplementsMultipleTemplateInterfacesWithSpecificTypes() {
compile(
"/**",
" * @template A_TYPE",
" * @interface",
" */",
"function A() {}",
"",
"/**",
" * @template B_TYPE",
" * @interface",
" */",
"function B() {}",
"",
"/**",
" * @constructor",
" * @implements {A<string>}",
" * @implements {B<number>}",
" */",
"function C() {}");
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
NominalType c = typeRegistry.getType("C");
assertImplementedTypes(a).isEmpty();
assertKnownImplementations(a).containsExactly(TYPE_C);
assertSubtypes(a).isEmpty();
assertImplementedTypes(b).isEmpty();
assertKnownImplementations(b).containsExactly(TYPE_C);
assertSubtypes(b).isEmpty();
assertImplementedTypes(c)
.containsExactly(
addTemplateTypes(TYPE_A, stringTypeExpression()),
addTemplateTypes(TYPE_B, numberTypeExpression()));
assertKnownImplementations(c).isEmpty();
assertSubtypes(c).isEmpty();
}
@Test
public void implementsInterfaceThatExtendsAnother_oneLevel() {
compile(
"/** @interface */",
"function A() {}",
"",
"/** @interface @extends {A} */",
"function B() {}",
"",
"/**",
" * @constructor",
" * @implements {B}",
" */",
"function C() {}");
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
NominalType c = typeRegistry.getType("C");
assertImplementedTypes(a).isEmpty();
assertKnownImplementations(a).containsExactly(TYPE_C);
assertSubtypes(a).containsExactly(TYPE_B);
assertImplementedTypes(b).containsExactly(TYPE_A);
assertKnownImplementations(b).containsExactly(TYPE_C);
assertSubtypes(b).isEmpty();
assertImplementedTypes(c).containsExactly(TYPE_A, TYPE_B);
assertKnownImplementations(c).isEmpty();
assertSubtypes(c).isEmpty();
}
@Test
public void implementsInterfaceThatExtendsAnother_multipleLevels() {
compile(
"/** @interface */",
"function A() {}",
"",
"/** @interface @extends {A} */",
"function B() {}",
"",
"/** @interface @extends {B} */",
"function C() {}",
"",
"/** @interface @extends {C} */",
"function D() {}",
"",
"/**",
" * @constructor",
" * @implements {D}",
" */",
"function E() {}");
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
NominalType c = typeRegistry.getType("C");
NominalType d = typeRegistry.getType("D");
NominalType e = typeRegistry.getType("E");
assertImplementedTypes(a).isEmpty();
assertKnownImplementations(a).containsExactly(TYPE_E);
assertSubtypes(a).containsExactly(TYPE_B, TYPE_C, TYPE_D);
assertImplementedTypes(b).containsExactly(TYPE_A);
assertKnownImplementations(b).containsExactly(TYPE_E);
assertSubtypes(b).containsExactly(TYPE_C, TYPE_D);
assertImplementedTypes(c).containsExactly(TYPE_A, TYPE_B);
assertKnownImplementations(c).containsExactly(TYPE_E);
assertSubtypes(c).containsExactly(TYPE_D);
assertImplementedTypes(d).containsExactly(TYPE_A, TYPE_B, TYPE_C);
assertKnownImplementations(d).containsExactly(TYPE_E);
assertSubtypes(d).isEmpty();
assertImplementedTypes(e).containsExactly(TYPE_A, TYPE_B, TYPE_C, TYPE_D);
assertKnownImplementations(e).isEmpty();
assertSubtypes(e).isEmpty();
}
@Test
public void implementsInterfaceThatExtendsAnother_multipleBranches() {
compile(
"/** @interface */",
"function A() {}",
"",
"/** @interface @extends {A} */",
"function B() {}",
"",
"/** @interface @extends {B} @extends {IThenable<string>} */",
"function C() {}",
"",
"/** @interface @extends {C} */",
"function D() {}",
"",
"/**",
" * @constructor",
" * @implements {D}",
" */",
"function E() {}");
NamedType thenable =
namedType("IThenable")
.toBuilder()
.setExtern(true)
.addTemplateType(stringTypeExpression())
.build();
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
NominalType c = typeRegistry.getType("C");
NominalType d = typeRegistry.getType("D");
NominalType e = typeRegistry.getType("E");
assertImplementedTypes(a).isEmpty();
assertKnownImplementations(a).containsExactly(TYPE_E);
assertSubtypes(a).containsExactly(TYPE_B, TYPE_C, TYPE_D);
assertImplementedTypes(b).containsExactly(TYPE_A);
assertKnownImplementations(b).containsExactly(TYPE_E);
assertSubtypes(b).containsExactly(TYPE_C, TYPE_D);
assertImplementedTypes(c).containsExactly(TYPE_A, TYPE_B, thenable);
assertKnownImplementations(c).containsExactly(TYPE_E);
assertSubtypes(c).containsExactly(TYPE_D);
assertImplementedTypes(d).containsExactly(TYPE_A, TYPE_B, TYPE_C, thenable);
assertKnownImplementations(d).containsExactly(TYPE_E);
assertSubtypes(d).isEmpty();
assertImplementedTypes(e).containsExactly(TYPE_A, TYPE_B, TYPE_C, TYPE_D, thenable);
assertKnownImplementations(e).isEmpty();
assertSubtypes(e).isEmpty();
}
@Test
public void implementsInterfaceThatExtendsAnother_templatizedImplementations() {
compile(
"/**",
" * @interface",
" * @template ATYPE",
" */",
"function A() {}",
"",
"/**",
" * @interface",
" * @extends {A<BTYPE>}",
" * @template BTYPE",
" */",
"function B() {}",
"",
"/** @constructor */",
"function C() {}",
"",
"/**",
" * @constructor",
" * @implements {B<!C>}",
" */",
"function D() {}");
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
NominalType c = typeRegistry.getType("C");
NominalType d = typeRegistry.getType("D");
assertImplementedTypes(a).isEmpty();
assertKnownImplementations(a).containsExactly(TYPE_D);
assertSubtypes(a).containsExactly(TYPE_B);
assertImplementedTypes(b)
.containsExactly(addTemplateTypes(TYPE_A, namedTypeExpression("BTYPE")));
assertKnownImplementations(b).containsExactly(TYPE_D);
assertSubtypes(b).isEmpty();
assertImplementedTypes(d)
.containsExactly(
addTemplateTypes(TYPE_A, TypeExpression.newBuilder().setNamedType(TYPE_C).build()),
addTemplateTypes(TYPE_B, TypeExpression.newBuilder().setNamedType(TYPE_C).build()));
assertKnownImplementations(d).isEmpty();
assertSubtypes(d).isEmpty();
}
@Test
public void implementsInterfaceThatExtendsAnother_templatizedImplementations_ithenableCheck() {
compile(
"/**",
" * @interface",
" * @extends {IThenable<ATYPE>}",
" * @template ATYPE",
" */",
"function A() {}",
"",
"/**",
" * @interface",
" * @extends {A<BTYPE>}",
" * @template BTYPE",
" */",
"function B() {}",
"",
"/** @constructor */",
"function C() {}",
"",
"/**",
" * @constructor",
" * @implements {B<!C>}",
" */",
"function D() {}");
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
NominalType c = typeRegistry.getType("C");
NominalType d = typeRegistry.getType("D");
NamedType thenable =
namedType("IThenable")
.toBuilder()
.setExtern(true)
.build();
assertImplementedTypes(a)
.containsExactly(
addTemplateTypes(thenable, namedTypeExpression("ATYPE")));
assertKnownImplementations(a).containsExactly(TYPE_D);
assertSubtypes(a).containsExactly(TYPE_B);
assertImplementedTypes(b)
.containsExactly(
addTemplateTypes(TYPE_A, namedTypeExpression("BTYPE")),
addTemplateTypes(thenable, namedTypeExpression("BTYPE")));
assertKnownImplementations(b).containsExactly(TYPE_D);
assertSubtypes(b).isEmpty();
assertImplementedTypes(d)
.containsExactly(
addTemplateTypes(TYPE_A, TypeExpression.newBuilder().setNamedType(TYPE_C).build()),
addTemplateTypes(TYPE_B, TypeExpression.newBuilder().setNamedType(TYPE_C).build()),
addTemplateTypes(thenable, TypeExpression.newBuilder().setNamedType(TYPE_C).build()));
assertKnownImplementations(d).isEmpty();
assertSubtypes(d).isEmpty();
}
@Test
public void implementsMultipleInterfacesThatExtendOthers() {
compile(
"/** @interface */",
"function A() {}",
"",
"/** @interface @extends {A} */",
"function B() {}",
"",
"/** @interface*/",
"function C() {}",
"",
"/** @interface @extends {C} */",
"function D() {}",
"",
"/**",
" * @constructor",
" * @implements {B}",
" * @implements {D}",
" */",
"function E() {}");
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
NominalType c = typeRegistry.getType("C");
NominalType d = typeRegistry.getType("D");
NominalType e = typeRegistry.getType("E");
assertImplementedTypes(a).isEmpty();
assertKnownImplementations(a).containsExactly(TYPE_E);
assertSubtypes(a).containsExactly(TYPE_B);
assertImplementedTypes(b).containsExactly(TYPE_A);
assertKnownImplementations(b).containsExactly(TYPE_E);
assertSubtypes(b).isEmpty();
assertImplementedTypes(c).isEmpty();
assertKnownImplementations(c).containsExactly(TYPE_E);
assertSubtypes(c).containsExactly(TYPE_D);
assertImplementedTypes(d).containsExactly(TYPE_C);
assertKnownImplementations(d).containsExactly(TYPE_E);
assertSubtypes(d).isEmpty();
assertImplementedTypes(e).containsExactly(TYPE_A, TYPE_B, TYPE_C, TYPE_D);
assertKnownImplementations(e).isEmpty();
assertSubtypes(e).isEmpty();
}
@Test
public void detectsImplementationsFromSuperType() {
compile(
"/** @interface */",
"function A() {}",
"",
"/** @implements {A} */",
"class B {}",
"class C extends B {}",
"class D extends C {}");
NominalType d = typeRegistry.getType("D");
assertImplementedTypes(d).containsExactly(TYPE_A);
NominalType a = typeRegistry.getType("A");
assertKnownImplementations(a).containsExactly(TYPE_B, TYPE_C, TYPE_D);
}
@Test
public void detectsImplementationsFromSuperType_duplicateImplementDeclarations() {
compile(
"/** @interface */",
"function A() {}",
"",
"/** @implements {A} */",
"class B {}",
"",
"/** @implements {A} */",
"class C extends B {}",
"class D extends C {}");
NominalType d = typeRegistry.getType("D");
assertImplementedTypes(d).containsExactly(TYPE_A);
}
@Test
public void detectsImplementationsFromSuperType_multipleImplementations() {
compile(
"/** @interface */ class A {}",
"/** @interface */ class B {}",
"",
"/** @implements {A} */ class C {}",
"/** @implements {B} */ class D extends C {}",
"class E extends D {}");
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
NominalType e = typeRegistry.getType("E");
assertKnownImplementations(a).containsExactly(TYPE_C, TYPE_D, TYPE_E);
assertKnownImplementations(b).containsExactly(TYPE_D, TYPE_E);
assertImplementedTypes(e).containsExactly(TYPE_A, TYPE_B);
}
@Test
public void detectsImplementationsFromSuperType_multipleTemplatizedImplementations() {
compile(
"/**",
" * @template A_TYPE",
" * @interface",
" */",
"class A {}",
"",
"/**",
" * @template B_TYPE",
" * @interface",
" */",
"class B {}",
"",
"/** @implements {A<string>} */ class C {}",
"/** @implements {B<number>} */ class D extends C {}",
"class E extends D {}");
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
NominalType e = typeRegistry.getType("E");
assertKnownImplementations(a).containsExactly(TYPE_C, TYPE_D, TYPE_E);
assertKnownImplementations(b).containsExactly(TYPE_D, TYPE_E);
assertImplementedTypes(e).containsExactly(
addTemplateTypes(TYPE_A, stringTypeExpression()),
addTemplateTypes(TYPE_B, numberTypeExpression()));
}
@Test
public void detectsImplementationsFromSuperType_cascadingTemplateTypes() {
compile(
"/**",
" * @template A_TYPE",
" * @interface",
" */",
"class A {}",
"",
"/**",
" * @template B_TYPE",
" * @interface",
" */",
"class B {}",
"",
"/**",
" * @template C_TYPE",
" * @implements {A<C_TYPE>}",
" */",
"class C {}",
"",
"/**",
" * @template D_TYPE",
" * @extends {C<D_TYPE>}",
" * @implements {B<D_TYPE>}",
" */",
"class D extends C {}",
"",
"/**",
" * @template E_TYPE",
" * @extends {D<E_TYPE>}",
" */",
"class E extends D {}");
NominalType e = typeRegistry.getType("E");
assertImplementedTypes(e).containsExactly(
addTemplateTypes(TYPE_A, namedTypeExpression("E_TYPE")),
addTemplateTypes(TYPE_B, namedTypeExpression("E_TYPE")));
}
@Test
public void getHierarchyForBaseClass() {
compile("class A {}");
NominalType type = typeRegistry.getType("A");
assertTypeHierarchy(type).containsExactly(TYPE_A);
}
@Test
public void getHierarchyForOneLevelOfInheritance() {
compile(
"class A {}",
"class B extends A {}");
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
assertTypeHierarchy(a).containsExactly(TYPE_A);
assertSubtypes(a).containsExactly(TYPE_B);
assertTypeHierarchy(b).containsExactly(TYPE_B, TYPE_A);
assertSubtypes(b).isEmpty();
}
@Test
public void getHierarchyForMultipleLevels() {
compile(
"class A {}",
"class B extends A {}",
"class C extends B {}");
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
NominalType c = typeRegistry.getType("C");
assertTypeHierarchy(a).containsExactly(TYPE_A);
assertSubtypes(a).containsExactly(TYPE_B);
assertTypeHierarchy(b).containsExactly(TYPE_B, TYPE_A);
assertSubtypes(b).containsExactly(TYPE_C);
assertTypeHierarchy(c).containsExactly(TYPE_C, TYPE_B, TYPE_A);
assertSubtypes(c).isEmpty();
}
@Test
public void getHierarchyForMultipleLevels_es5Constructors() {
compile(
"/** @constructor */ function A() {}",
"/** @constructor @extends {A} */ function B() {}",
"/** @constructor @extends {B} */ function C() {}");
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
NominalType c = typeRegistry.getType("C");
assertTypeHierarchy(a).containsExactly(TYPE_A);
assertSubtypes(a).containsExactly(TYPE_B);
assertTypeHierarchy(b).containsExactly(TYPE_B, TYPE_A);
assertSubtypes(b).containsExactly(TYPE_C);
assertTypeHierarchy(c).containsExactly(TYPE_C, TYPE_B, TYPE_A);
assertSubtypes(c).isEmpty();
}
@Test
public void getHierarchyForTemplatizedTypes_es5Constructors() {
compile(
"/**",
" * @constructor",
" * @template A_TYPE",
" */",
"function A() {}",
"/**",
" * @constructor",
" * @extends {A<B_TYPE>}",
" * @template B_TYPE",
" */",
"function B() {}",
"/**",
" * @constructor",
" * @extends {B<C_TYPE>}",
" * @template C_TYPE",
" */",
"function C() {}");
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
NominalType c = typeRegistry.getType("C");
assertImplementedTypes(a).isEmpty();
assertKnownImplementations(a).isEmpty();
assertSubtypes(a).containsExactly(
addTemplateTypes(TYPE_B, namedTypeExpression("B_TYPE")));
assertTypeHierarchy(a).containsExactly(
addTemplateTypes(TYPE_A, namedTypeExpression("A_TYPE")));
assertImplementedTypes(b).isEmpty();
assertKnownImplementations(b).isEmpty();
assertSubtypes(b).containsExactly(
addTemplateTypes(TYPE_C, namedTypeExpression("C_TYPE")));
assertTypeHierarchy(b).containsExactly(
addTemplateTypes(TYPE_B, namedTypeExpression("B_TYPE")),
addTemplateTypes(TYPE_A, namedTypeExpression("B_TYPE")));
assertImplementedTypes(c).isEmpty();
assertKnownImplementations(c).isEmpty();
assertSubtypes(c).isEmpty();
assertTypeHierarchy(c).containsExactly(
addTemplateTypes(TYPE_C, namedTypeExpression("C_TYPE")),
addTemplateTypes(TYPE_B, namedTypeExpression("C_TYPE")),
addTemplateTypes(TYPE_A, namedTypeExpression("C_TYPE")));
}
@Test
public void getHierarchyForTemplatizedTypes_es6Classes() {
compile(
"/**",
" * @template A_TYPE",
" */",
"class A {}",
"/**",
" * @extends {A<B_TYPE>}",
" * @template B_TYPE",
" */",
"class B extends A {}",
"/**",
" * @extends {B<C_TYPE>}",
" * @template C_TYPE",
" */",
"class C extends B {}");
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
NominalType c = typeRegistry.getType("C");
assertImplementedTypes(a).isEmpty();
assertKnownImplementations(a).isEmpty();
assertSubtypes(a).containsExactly(
addTemplateTypes(TYPE_B, namedTypeExpression("B_TYPE")));
assertTypeHierarchy(a).containsExactly(
addTemplateTypes(TYPE_A, namedTypeExpression("A_TYPE")));
assertImplementedTypes(b).isEmpty();
assertKnownImplementations(b).isEmpty();
assertSubtypes(b).containsExactly(
addTemplateTypes(TYPE_C, namedTypeExpression("C_TYPE")));
assertTypeHierarchy(b).containsExactly(
addTemplateTypes(TYPE_B, namedTypeExpression("B_TYPE")),
addTemplateTypes(TYPE_A, namedTypeExpression("B_TYPE")));
assertImplementedTypes(c).isEmpty();
assertKnownImplementations(c).isEmpty();
assertSubtypes(c).isEmpty();
assertTypeHierarchy(c).containsExactly(
addTemplateTypes(TYPE_C, namedTypeExpression("C_TYPE")),
addTemplateTypes(TYPE_B, namedTypeExpression("C_TYPE")),
addTemplateTypes(TYPE_A, namedTypeExpression("C_TYPE")));
}
@Test
public void getHierarchyForTemplatizedTypes_leafHasConcreteType() {
compile(
"/**",
" * @template A_TYPE",
" */",
"class A {}",
"/**",
" * @extends {A<B_TYPE>}",
" * @template B_TYPE",
" */",
"class B extends A {}",
"/**",
" * @extends {B<C_TYPE>}",
" * @template C_TYPE",
" */",
"class C extends B {}",
"",
"/** @extends {C<string>} */",
"class D extends C {}");
NominalType a = typeRegistry.getType("A");
NominalType b = typeRegistry.getType("B");
NominalType c = typeRegistry.getType("C");
NominalType d = typeRegistry.getType("D");
assertImplementedTypes(a).isEmpty();
assertKnownImplementations(a).isEmpty();
assertSubtypes(a).containsExactly(
addTemplateTypes(TYPE_B, namedTypeExpression("B_TYPE")));
assertTypeHierarchy(a).containsExactly(
addTemplateTypes(TYPE_A, namedTypeExpression("A_TYPE")));
assertImplementedTypes(b).isEmpty();
assertKnownImplementations(b).isEmpty();
assertSubtypes(b).containsExactly(
addTemplateTypes(TYPE_C, namedTypeExpression("C_TYPE")));
assertTypeHierarchy(b).containsExactly(
addTemplateTypes(TYPE_B, namedTypeExpression("B_TYPE")),
addTemplateTypes(TYPE_A, namedTypeExpression("B_TYPE")));
assertImplementedTypes(c).isEmpty();
assertKnownImplementations(c).isEmpty();
assertSubtypes(c).containsExactly(TYPE_D);
assertTypeHierarchy(c).containsExactly(
addTemplateTypes(TYPE_C, namedTypeExpression("C_TYPE")),
addTemplateTypes(TYPE_B, namedTypeExpression("C_TYPE")),
addTemplateTypes(TYPE_A, namedTypeExpression("C_TYPE")));
assertImplementedTypes(d).isEmpty();
assertKnownImplementations(d).isEmpty();
assertSubtypes(d).isEmpty();
assertTypeHierarchy(d).containsExactly(
TYPE_D,
addTemplateTypes(TYPE_C, stringTypeExpression()),
addTemplateTypes(TYPE_B, stringTypeExpression()),
addTemplateTypes(TYPE_A, stringTypeExpression()));
}
@Test
public void tracksSubTypesFromDifferentModulesWithSameBaseName() {
util.compile(
createSourceFile(
fs.getPath("/src/modules/one.js"),
"export class A {}"),
createSourceFile(
fs.getPath("/src/modules/two.js"),
"import {A} from './one';",
"export class B extends A {}"),
createSourceFile(
fs.getPath("/src/modules/three.js"),
"import {A} from './one';",
"export class B extends A {}"));
NominalType a = typeRegistry.getType("module$$src$modules$one.A");
assertSubtypes(a)
.containsExactly(
TYPE_B.toBuilder()
.setQualifiedName("three.B")
.setLink(typeLink("three_exports_B.html"))
.build(),
TYPE_B.toBuilder()
.setQualifiedName("two.B")
.setLink(typeLink("two_exports_B.html"))
.build());
}
private IterableOfProtosSubject<?, NamedType, Iterable<NamedType>> assertImplementedTypes(
NominalType type) {
return assertThat(typeInspectorFactory.create(type).getImplementedTypes());
}
private IterableOfProtosSubject<?, NamedType, Iterable<NamedType>> assertKnownImplementations(
NominalType type) {
return assertThat(typeInspectorFactory.create(type).getKnownImplementations());
}
private IterableOfProtosSubject<?, NamedType, Iterable<NamedType>> assertSubtypes(
NominalType type) {
return assertThat(typeInspectorFactory.create(type).getSubtypes());
}
private IterableOfProtosSubject<?, NamedType, Iterable<NamedType>> assertTypeHierarchy(
NominalType type) {
return assertThat(typeInspectorFactory.create(type).getTypeHierarchy());
}
}