From c29bed0f050fdaffe7c39fcafbe5d9f6cf392019 Mon Sep 17 00:00:00 2001 From: Oleg Schelicalnov Date: Tue, 23 Jul 2019 19:46:03 +0300 Subject: [PATCH 1/3] Add support generic bounds --- .../generator/DefaultTypeProcessor.java | 25 ++++++++++++-- .../habarta/typescript/generator/TsType.java | 15 ++++++++ .../generator/compiler/ModelCompiler.java | 34 ++++++++++++++----- .../generator/emitter/TsBeanModel.java | 8 ++--- .../typescript/generator/EnumTest.java | 2 +- .../typescript/generator/GenericsTest.java | 19 +++++++++++ 6 files changed, 87 insertions(+), 16 deletions(-) diff --git a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/DefaultTypeProcessor.java b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/DefaultTypeProcessor.java index f883d7eeb..321b5d06c 100644 --- a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/DefaultTypeProcessor.java +++ b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/DefaultTypeProcessor.java @@ -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 tsTypeArguments = new ArrayList<>(); + final List> discoveredClasses = new ArrayList<>(); for (int i = 0; i < javaClass.getTypeParameters().length; i++) { - tsTypeArguments.add(TsType.Any); + TypeVariable typeVariable = javaClass.getTypeParameters()[i]; + final List 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); diff --git a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/TsType.java b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/TsType.java index b9eef5d7c..7996a8e95 100644 --- a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/TsType.java +++ b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/TsType.java @@ -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); diff --git a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java index 5c91e7482..f5b9c9405 100644 --- a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java +++ b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java @@ -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 restApplicationsWithInterface = model.getRestApplications().stream() .filter(restApplication -> restApplication.getType().generateInterface.apply(settings)) @@ -347,10 +347,26 @@ private boolean mappedToClass(Class cls) { return cls != null && !cls.isInterface() && settings.getMapClassesAsClassesFilter().test(cls.getName()); } - private static List getTypeParameters(Class cls) { - final List typeParameters = new ArrayList<>(); + private List getTypeParameters(Class cls) { + final List typeParameters = new ArrayList<>(); for (TypeVariable typeParameter : cls.getTypeParameters()) { - typeParameters.add(new TsType.GenericVariableType(typeParameter.getName())); + final List bounds = new ArrayList<>(); + for (Type bound : typeParameter.getBounds()) { + if (!Object.class.equals(bound)) { + bounds.add(javaToTypeScript(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; } @@ -598,8 +614,8 @@ private Symbol createRestResponseType(SymbolTable symbolTable, TsModel tsModel) } private void createRestInterfaces(TsModel tsModel, SymbolTable symbolTable, List restApplications, - Symbol responseSymbol, TsType.GenericVariableType optionsGenericVariable, TsType optionsType) { - final List typeParameters = Utils.listFromNullable(optionsGenericVariable); + Symbol responseSymbol, TsType.BoundedGenericVariableType optionsGenericVariable, TsType optionsType) { + final List typeParameters = Utils.listFromNullable(optionsGenericVariable); final Map> groupedMethods = processRestMethods(tsModel, restApplications, symbolTable, null, responseSymbol, optionsType, false); for (Map.Entry> entry : groupedMethods.entrySet()) { final TsBeanModel interfaceModel = new TsBeanModel(null, TsBeanCategory.Service, false, entry.getKey(), typeParameters, null, null, null, null, null, entry.getValue(), null); @@ -608,9 +624,9 @@ private void createRestInterfaces(TsModel tsModel, SymbolTable symbolTable, List } private void createRestClients(TsModel tsModel, SymbolTable symbolTable, List restApplications, - Symbol responseSymbol, TsType.GenericVariableType optionsGenericVariable, TsType optionsType) { + Symbol responseSymbol, TsType.BoundedGenericVariableType optionsGenericVariable, TsType optionsType) { final Symbol httpClientSymbol = symbolTable.getSyntheticSymbol("HttpClient"); - final List typeParameters = Utils.listFromNullable(optionsGenericVariable); + final List typeParameters = Utils.listFromNullable(optionsGenericVariable); // HttpClient interface final TsType.GenericVariableType returnGenericVariable = new TsType.GenericVariableType("R"); diff --git a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/emitter/TsBeanModel.java b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/emitter/TsBeanModel.java index 96984bae7..214fbc37b 100644 --- a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/emitter/TsBeanModel.java +++ b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/emitter/TsBeanModel.java @@ -12,7 +12,7 @@ public class TsBeanModel extends TsDeclarationModel { private final boolean isClass; private final List decorators; - private final List typeParameters; + private final List typeParameters; private final TsType parent; private final List extendsList; private final List implementsList; @@ -29,7 +29,7 @@ public TsBeanModel( TsBeanCategory category, boolean isClass, Symbol name, - List typeParameters, + List typeParameters, TsType parent, List extendsList, List implementsList, @@ -46,7 +46,7 @@ private TsBeanModel( boolean isClass, List decorators, Symbol name, - List typeParameters, + List typeParameters, TsType parent, List extendsList, List implementsList, @@ -86,7 +86,7 @@ public TsBeanModel withDecorators(List decorators) { return new TsBeanModel(origin, category, isClass, decorators, name, typeParameters, parent, extendsList, implementsList, taggedUnionClasses, discriminantProperty, discriminantLiteral, taggedUnionAlias, properties, constructor, methods, comments); } - public List getTypeParameters() { + public List getTypeParameters() { return typeParameters; } diff --git a/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/EnumTest.java b/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/EnumTest.java index e30d9b2d5..f2694838d 100644 --- a/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/EnumTest.java +++ b/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/EnumTest.java @@ -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")); + assertTrue(output.contains("interface Enum>")); } private static @interface Child { diff --git a/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/GenericsTest.java b/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/GenericsTest.java index f6aaeef89..d83239b7c 100644 --- a/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/GenericsTest.java +++ b/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/GenericsTest.java @@ -147,6 +147,21 @@ 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 {" + nl + + " x: T;" + nl + + "}" + nl + + "" + nl + + "interface F {" + nl + + "}"; + assertEquals(expected, output.trim()); + } + class A { public A x; public A, List> y; @@ -170,6 +185,10 @@ class E extends D { class F { } + class G { + public T x; + } + abstract class IA implements IB, Comparable { } From fcfc1f6ad8adeedbd9cc1d7efcf31c49a1f5ac5c Mon Sep 17 00:00:00 2001 From: Oleg Schelicalnov Date: Wed, 24 Jul 2019 11:10:39 +0300 Subject: [PATCH 2/3] Fix recursive call to javaToTypeScript. --- .../generator/compiler/ModelCompiler.java | 6 +++--- .../typescript/generator/GenericsTest.java | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java index f5b9c9405..a4aa4b529 100644 --- a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java +++ b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java @@ -330,7 +330,7 @@ private TsBeanModel processBean(SymbolTable symbolTable, Model model, Map cls) { return cls != null && !cls.isInterface() && settings.getMapClassesAsClassesFilter().test(cls.getName()); } - private List getTypeParameters(Class cls) { + private List getTypeParameters(SymbolTable symbolTable, Class cls) { final List typeParameters = new ArrayList<>(); for (TypeVariable typeParameter : cls.getTypeParameters()) { final List bounds = new ArrayList<>(); for (Type bound : typeParameter.getBounds()) { if (!Object.class.equals(bound)) { - bounds.add(javaToTypeScript(bound)); + bounds.add(typeFromJava(symbolTable, bound)); } } switch (bounds.size()) { diff --git a/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/GenericsTest.java b/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/GenericsTest.java index d83239b7c..bce46eee7 100644 --- a/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/GenericsTest.java +++ b/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/GenericsTest.java @@ -162,6 +162,19 @@ public void testGenericBoundsParameter() { 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> {" + nl + + " x: T;" + nl + + " y: S;" + nl + + "}"; + assertEquals(expected, output.trim()); + } + class A { public A x; public A, List> y; @@ -189,6 +202,11 @@ class G { public T x; } + class H> { + public T x; + public S y; + } + abstract class IA implements IB, Comparable { } From 085e5b3f1a5f5e1f42a421f64b9ac76d7b1cb620 Mon Sep 17 00:00:00 2001 From: Oleg Schelicalnov Date: Fri, 11 Jun 2021 13:16:38 +0300 Subject: [PATCH 3/3] Fix after rebase --- .../typescript/generator/compiler/ModelCompiler.java | 2 +- .../habarta/typescript/generator/emitter/TsAliasModel.java | 6 +++--- .../test/java/cz/habarta/typescript/generator/KotlinTest.kt | 2 +- .../cz/habarta/typescript/generator/TaggedUnionsTest.java | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java index a4aa4b529..85e691a5e 100644 --- a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java +++ b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java @@ -535,7 +535,7 @@ private TsModel addConstructors(SymbolTable symbolTable, TsModel tsModel) { final List beans = new ArrayList<>(); for (TsBeanModel bean : tsModel.getBeans()) { final Symbol beanIdentifier = symbolTable.getSymbol(bean.getOrigin()); - final List typeParameters = getTypeParameters(bean.getOrigin()); + final List typeParameters = getTypeParameters(symbolTable, bean.getOrigin()); final TsType.ReferenceType dataType = typeParameters.isEmpty() ? new TsType.ReferenceType(beanIdentifier) : new TsType.GenericReferenceType(beanIdentifier, typeParameters); diff --git a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/emitter/TsAliasModel.java b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/emitter/TsAliasModel.java index 76072201b..20f8fdb9c 100644 --- a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/emitter/TsAliasModel.java +++ b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/emitter/TsAliasModel.java @@ -9,16 +9,16 @@ public class TsAliasModel extends TsDeclarationModel { - private final List typeParameters; + private final List typeParameters; private final TsType definition; - public TsAliasModel(Class origin, Symbol name, List typeParameters, TsType definition, List comments) { + public TsAliasModel(Class origin, Symbol name, List typeParameters, TsType definition, List comments) { super(origin, null, name, comments); this.typeParameters = typeParameters != null ? typeParameters : Collections.emptyList(); this.definition = definition; } - public List getTypeParameters() { + public List getTypeParameters() { return typeParameters; } diff --git a/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/KotlinTest.kt b/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/KotlinTest.kt index 644816540..6ab7f4ab4 100644 --- a/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/KotlinTest.kt +++ b/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/KotlinTest.kt @@ -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")) + Assert.assertTrue(errorMessage, output.contains("interface A2>")) } private class A2 where S : Enum { diff --git a/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/TaggedUnionsTest.java b/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/TaggedUnionsTest.java index 6243dbda0..6e8dafc16 100644 --- a/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/TaggedUnionsTest.java +++ b/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/TaggedUnionsTest.java @@ -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")); + Assert.assertTrue(output.contains("VehiculeUnion")); } public static void main(String[] args) throws Exception {