Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support generic bounds #389

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,31 @@ public Result processType(Type javaType, Context context) {
// generic structural type used without type arguments
if (javaClass.getTypeParameters().length > 0) {
final List<TsType> tsTypeArguments = new ArrayList<>();
final List<Class<?>> discoveredClasses = new ArrayList<>();
for (int i = 0; i < javaClass.getTypeParameters().length; i++) {
tsTypeArguments.add(TsType.Any);
TypeVariable<?> typeVariable = javaClass.getTypeParameters()[i];
final List<TsType> bounds = new ArrayList<>();
for (int j = 0; j < typeVariable.getBounds().length; j++) {
Type boundType = typeVariable.getBounds()[j];
if (!Object.class.equals(boundType)) {
Result res = context.processType(boundType);
bounds.add(res.getTsType());
discoveredClasses.addAll(res.getDiscoveredClasses());
}
}
switch (bounds.size()) {
case 0:
tsTypeArguments.add(TsType.Any);
break;
case 1:
tsTypeArguments.add(bounds.get(0));
break;
default:
tsTypeArguments.add(new TsType.IntersectionType(bounds));
break;
}
}
return new Result(new TsType.GenericReferenceType(context.getSymbol(javaClass), tsTypeArguments));
return new Result(new TsType.GenericReferenceType(context.getSymbol(javaClass), tsTypeArguments), discoveredClasses);
}
// structural type
return new Result(new TsType.ReferenceType(context.getSymbol(javaClass)), javaClass);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,21 @@ public GenericVariableType(String name) {

}

public static class BoundedGenericVariableType extends GenericVariableType {

public final TsType bound;

public BoundedGenericVariableType(String name, TsType bound) {
super(name);
this.bound = bound;
}

@Override
public String format(Settings settings) {
return super.format(settings) + (bound != null ? " extends " + bound.format(settings) : "");
}
}

public static class EnumReferenceType extends ReferenceType {
public EnumReferenceType(Symbol symbol) {
super(symbol);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ public TsModel javaToTypeScript(Model model) {
final TsType optionsType = settings.restOptionsType != null
? new TsType.VerbatimType(settings.restOptionsType)
: null;
final TsType.GenericVariableType optionsGenericVariable = settings.restOptionsTypeIsGeneric
? new TsType.GenericVariableType(settings.restOptionsType)
final TsType.BoundedGenericVariableType optionsGenericVariable = settings.restOptionsTypeIsGeneric
? new TsType.BoundedGenericVariableType(settings.restOptionsType, null)
: null;
final List<RestApplicationModel> restApplicationsWithInterface = model.getRestApplications().stream()
.filter(restApplication -> restApplication.getType().generateInterface.apply(settings))
Expand Down Expand Up @@ -330,7 +330,7 @@ private <T> TsBeanModel processBean(SymbolTable symbolTable, Model model, Map<Ty
TsBeanCategory.Data,
isClass,
symbolTable.getSymbol(bean.getOrigin()),
getTypeParameters(bean.getOrigin()),
getTypeParameters(symbolTable, bean.getOrigin()),
parentType,
extendsList,
implementsList,
Expand All @@ -347,10 +347,26 @@ private boolean mappedToClass(Class<?> cls) {
return cls != null && !cls.isInterface() && settings.getMapClassesAsClassesFilter().test(cls.getName());
}

private static List<TsType.GenericVariableType> getTypeParameters(Class<?> cls) {
final List<TsType.GenericVariableType> typeParameters = new ArrayList<>();
private List<TsType.BoundedGenericVariableType> getTypeParameters(SymbolTable symbolTable, Class<?> cls) {
final List<TsType.BoundedGenericVariableType> typeParameters = new ArrayList<>();
for (TypeVariable<?> typeParameter : cls.getTypeParameters()) {
typeParameters.add(new TsType.GenericVariableType(typeParameter.getName()));
final List<TsType> bounds = new ArrayList<>();
for (Type bound : typeParameter.getBounds()) {
if (!Object.class.equals(bound)) {
bounds.add(typeFromJava(symbolTable, bound));
}
}
switch (bounds.size()) {
case 0:
typeParameters.add(new TsType.BoundedGenericVariableType(typeParameter.getName(), null));
break;
case 1:
typeParameters.add(new TsType.BoundedGenericVariableType(typeParameter.getName(), bounds.get(0)));
break;
default:
typeParameters.add(new TsType.BoundedGenericVariableType(typeParameter.getName(), new TsType.IntersectionType(bounds)));
break;
}
}
return typeParameters;
}
Expand Down Expand Up @@ -519,7 +535,7 @@ private TsModel addConstructors(SymbolTable symbolTable, TsModel tsModel) {
final List<TsBeanModel> beans = new ArrayList<>();
for (TsBeanModel bean : tsModel.getBeans()) {
final Symbol beanIdentifier = symbolTable.getSymbol(bean.getOrigin());
final List<TsType.GenericVariableType> typeParameters = getTypeParameters(bean.getOrigin());
final List<TsType.BoundedGenericVariableType> typeParameters = getTypeParameters(symbolTable, bean.getOrigin());
final TsType.ReferenceType dataType = typeParameters.isEmpty()
? new TsType.ReferenceType(beanIdentifier)
: new TsType.GenericReferenceType(beanIdentifier, typeParameters);
Expand Down Expand Up @@ -598,8 +614,8 @@ private Symbol createRestResponseType(SymbolTable symbolTable, TsModel tsModel)
}

private void createRestInterfaces(TsModel tsModel, SymbolTable symbolTable, List<RestApplicationModel> restApplications,
Symbol responseSymbol, TsType.GenericVariableType optionsGenericVariable, TsType optionsType) {
final List<TsType.GenericVariableType> typeParameters = Utils.listFromNullable(optionsGenericVariable);
Symbol responseSymbol, TsType.BoundedGenericVariableType optionsGenericVariable, TsType optionsType) {
final List<TsType.BoundedGenericVariableType> typeParameters = Utils.listFromNullable(optionsGenericVariable);
final Map<Symbol, List<TsMethodModel>> groupedMethods = processRestMethods(tsModel, restApplications, symbolTable, null, responseSymbol, optionsType, false);
for (Map.Entry<Symbol, List<TsMethodModel>> entry : groupedMethods.entrySet()) {
final TsBeanModel interfaceModel = new TsBeanModel(null, TsBeanCategory.Service, false, entry.getKey(), typeParameters, null, null, null, null, null, entry.getValue(), null);
Expand All @@ -608,9 +624,9 @@ private void createRestInterfaces(TsModel tsModel, SymbolTable symbolTable, List
}

private void createRestClients(TsModel tsModel, SymbolTable symbolTable, List<RestApplicationModel> restApplications,
Symbol responseSymbol, TsType.GenericVariableType optionsGenericVariable, TsType optionsType) {
Symbol responseSymbol, TsType.BoundedGenericVariableType optionsGenericVariable, TsType optionsType) {
final Symbol httpClientSymbol = symbolTable.getSyntheticSymbol("HttpClient");
final List<TsType.GenericVariableType> typeParameters = Utils.listFromNullable(optionsGenericVariable);
final List<TsType.BoundedGenericVariableType> typeParameters = Utils.listFromNullable(optionsGenericVariable);

// HttpClient interface
final TsType.GenericVariableType returnGenericVariable = new TsType.GenericVariableType("R");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@

public class TsAliasModel extends TsDeclarationModel {

private final List<TsType.GenericVariableType> typeParameters;
private final List<? extends TsType.GenericVariableType> typeParameters;
private final TsType definition;

public TsAliasModel(Class<?> origin, Symbol name, List<TsType.GenericVariableType> typeParameters, TsType definition, List<String> comments) {
public TsAliasModel(Class<?> origin, Symbol name, List<? extends TsType.GenericVariableType> typeParameters, TsType definition, List<String> comments) {
super(origin, null, name, comments);
this.typeParameters = typeParameters != null ? typeParameters : Collections.emptyList();
this.definition = definition;
}

public List<TsType.GenericVariableType> getTypeParameters() {
public List<? extends TsType.GenericVariableType> getTypeParameters() {
return typeParameters;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class TsBeanModel extends TsDeclarationModel {

private final boolean isClass;
private final List<TsDecorator> decorators;
private final List<TsType.GenericVariableType> typeParameters;
private final List<TsType.BoundedGenericVariableType> typeParameters;
private final TsType parent;
private final List<TsType> extendsList;
private final List<TsType> implementsList;
Expand All @@ -29,7 +29,7 @@ public TsBeanModel(
TsBeanCategory category,
boolean isClass,
Symbol name,
List<TsType.GenericVariableType> typeParameters,
List<TsType.BoundedGenericVariableType> typeParameters,
TsType parent,
List<TsType> extendsList,
List<TsType> implementsList,
Expand All @@ -46,7 +46,7 @@ private TsBeanModel(
boolean isClass,
List<TsDecorator> decorators,
Symbol name,
List<TsType.GenericVariableType> typeParameters,
List<TsType.BoundedGenericVariableType> typeParameters,
TsType parent,
List<TsType> extendsList,
List<TsType> implementsList,
Expand Down Expand Up @@ -86,7 +86,7 @@ public TsBeanModel withDecorators(List<TsDecorator> decorators) {
return new TsBeanModel(origin, category, isClass, decorators, name, typeParameters, parent, extendsList, implementsList, taggedUnionClasses, discriminantProperty, discriminantLiteral, taggedUnionAlias, properties, constructor, methods, comments);
}

public List<TsType.GenericVariableType> getTypeParameters() {
public List<TsType.BoundedGenericVariableType> getTypeParameters() {
return typeParameters;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ public void testObjectEnum() {
public void testJavaLangEnum1() {
final Settings settings = TestUtils.settings();
final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(Child.NoEnumFactory.class));
assertTrue(output.contains("interface Enum<E>"));
assertTrue(output.contains("interface Enum<E extends Enum<E>>"));
}

private static @interface Child {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,34 @@ public void testArbitraryGenericParameter() {
assertEquals(expected, output.trim());
}

@Test
public void testGenericBoundsParameter() {
final Settings settings = TestUtils.settings();
final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(G.class));
final String nl = settings.newline;
final String expected =
"interface G<T extends F> {" + nl +
" x: T;" + nl +
"}" + nl +
"" + nl +
"interface F {" + nl +
"}";
assertEquals(expected, output.trim());
}

@Test
public void testGenericRecirsiveBoundsParameter() {
final Settings settings = TestUtils.settings();
final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(H.class));
final String nl = settings.newline;
final String expected =
"interface H<T, S extends H<T, S>> {" + nl +
" x: T;" + nl +
" y: S;" + nl +
"}";
assertEquals(expected, output.trim());
}

class A<U,V> {
public A<String, String> x;
public A<A<String, B>, List<String>> y;
Expand All @@ -170,6 +198,15 @@ class E extends D<F> {
class F {
}

class G<T extends F> {
public T x;
}

class H<T, S extends H<T, S>> {
public T x;
public S y;
}

abstract class IA implements IB<String>, Comparable<IA> {
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class KotlinTest {
val settings = TestUtils.settings()
val output = TypeScriptGenerator(settings).generateTypeScript(Input.from(A2::class.java))
val errorMessage = "Unexpected output: $output"
Assert.assertTrue(errorMessage, output.contains("interface A2<S>"))
Assert.assertTrue(errorMessage, output.contains("interface A2<S extends Enum<S>>"))
}

private class A2<S> where S : Enum<S> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ public void testWithTypeParameter() {
final Settings settings = TestUtils.settings();
final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(Earth.class));
Assert.assertTrue(output.contains("EngineUnion"));
Assert.assertTrue(output.contains("VehiculeUnion<M>"));
Assert.assertTrue(output.contains("VehiculeUnion<M extends Engine>"));
}

public static void main(String[] args) throws Exception {
Expand Down