Skip to content

Commit 19518f4

Browse files
Merge pull request #97 from vojtechhabarta/classes
Possibility to generate TypeScript classes
2 parents c861638 + 2463c2d commit 19518f4

File tree

15 files changed

+396
-139
lines changed

15 files changed

+396
-139
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
package cz.habarta.typescript.generator;
3+
4+
5+
public enum ClassMapping {
6+
asInterfaces, asClasses;
7+
}

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/Settings.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public class Settings {
3535
public Map<String, String> customTypeMappings = new LinkedHashMap<>();
3636
public DateMapping mapDate; // default is DateMapping.asDate
3737
public EnumMapping mapEnum; // default is EnumMapping.asUnion
38+
public ClassMapping mapClasses; // default is ClassMapping.asInterfaces
3839
public boolean disableTaggedUnions = false;
3940
public TypeProcessor customTypeProcessor = null;
4041
public boolean sortDeclarations = false;
@@ -108,6 +109,9 @@ public void validate() {
108109
emitterExtension.getClass().getSimpleName()));
109110
}
110111
}
112+
if (mapClasses == ClassMapping.asClasses) {
113+
throw new RuntimeException("'mapClasses' parameter is set to `asClasses` which generates runtime code but 'outputFileType' parameter is not set to 'implementationFile'.");
114+
}
111115
}
112116
}
113117

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java

Lines changed: 130 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import cz.habarta.typescript.generator.*;
55
import cz.habarta.typescript.generator.emitter.*;
66
import cz.habarta.typescript.generator.parser.*;
7+
import cz.habarta.typescript.generator.util.Utils;
78
import java.lang.reflect.*;
89
import java.util.*;
910

@@ -41,6 +42,8 @@ public ModelCompiler(Settings settings, TypeProcessor typeProcessor) {
4142
public TsModel javaToTypeScript(Model model) {
4243
final SymbolTable symbolTable = new SymbolTable(settings);
4344
TsModel tsModel = processModel(symbolTable, model);
45+
tsModel = removeInheritedProperties(symbolTable, tsModel);
46+
tsModel = addImplementedProperties(symbolTable, tsModel);
4447

4548
// dates
4649
tsModel = transformDates(symbolTable, tsModel);
@@ -59,6 +62,7 @@ public TsModel javaToTypeScript(Model model) {
5962
tsModel = createAndUseTaggedUnions(symbolTable, tsModel);
6063

6164
symbolTable.resolveSymbolNames();
65+
tsModel = sortDeclarations(symbolTable, tsModel);
6266
return tsModel;
6367
}
6468

@@ -86,7 +90,7 @@ private TsModel processModel(SymbolTable symbolTable, Model model) {
8690
private Map<Type, List<BeanModel>> createChildrenMap(Model model) {
8791
final Map<Type, List<BeanModel>> children = new LinkedHashMap<>();
8892
for (BeanModel bean : model.getBeans()) {
89-
for (Type ancestor : bean.getDirectAncestors()) {
93+
for (Type ancestor : bean.getParentAndInterfaces()) {
9094
if (!children.containsKey(ancestor)) {
9195
children.put(ancestor, new ArrayList<BeanModel>());
9296
}
@@ -128,7 +132,8 @@ private TsBeanModel processBean(SymbolTable symbolTable, Map<Type, List<BeanMode
128132
properties.add(0, new TsPropertyModel(bean.getDiscriminantProperty(), discriminantType, null));
129133
}
130134

131-
return new TsBeanModel(bean.getOrigin(), beanType, parentType, bean.getTaggedUnionClasses(), interfaces, properties, bean.getComments());
135+
final boolean isClass = !bean.getOrigin().isInterface() && settings.mapClasses == ClassMapping.asClasses;
136+
return new TsBeanModel(bean.getOrigin(), isClass, beanType, parentType, bean.getTaggedUnionClasses(), interfaces, properties, bean.getComments());
132137
}
133138

134139
private static List<BeanModel> getSelfAndDescendants(BeanModel bean, Map<Type, List<BeanModel>> children) {
@@ -185,6 +190,76 @@ private TsType typeFromJava(SymbolTable symbolTable, Type javaType, String usedI
185190
}
186191
}
187192

193+
private TsModel removeInheritedProperties(SymbolTable symbolTable, TsModel tsModel) {
194+
final List<TsBeanModel> beans = new ArrayList<>();
195+
for (TsBeanModel bean : tsModel.getBeans()) {
196+
final Map<String, TsType> inheritedPropertyTypes = getInheritedProperties(symbolTable, tsModel, bean.getParentAndInterfaces());
197+
final List<TsPropertyModel> properties = new ArrayList<>();
198+
for (TsPropertyModel property : bean.getProperties()) {
199+
if (!Objects.equals(property.getTsType(), inheritedPropertyTypes.get(property.getName()))) {
200+
properties.add(property);
201+
}
202+
}
203+
beans.add(bean.withProperties(properties));
204+
}
205+
return tsModel.setBeans(beans);
206+
}
207+
208+
private TsModel addImplementedProperties(SymbolTable symbolTable, TsModel tsModel) {
209+
final List<TsBeanModel> beans = new ArrayList<>();
210+
for (TsBeanModel bean : tsModel.getBeans()) {
211+
if (bean.isClass()) {
212+
final List<TsPropertyModel> resultProperties = new ArrayList<>(bean.getProperties());
213+
214+
final Set<String> classPropertyNames = new LinkedHashSet<>();
215+
for (TsPropertyModel property : bean.getProperties()) {
216+
classPropertyNames.add(property.getName());
217+
}
218+
classPropertyNames.addAll(getInheritedProperties(symbolTable, tsModel, Utils.listFromNullable(bean.getParent())).keySet());
219+
220+
final List<TsPropertyModel> implementedProperties = getImplementedProperties(symbolTable, tsModel, bean.getInterfaces());
221+
Collections.reverse(implementedProperties);
222+
for (TsPropertyModel implementedProperty : implementedProperties) {
223+
if (!classPropertyNames.contains(implementedProperty.getName())) {
224+
resultProperties.add(0, implementedProperty);
225+
classPropertyNames.add(implementedProperty.getName());
226+
}
227+
}
228+
229+
beans.add(bean.withProperties(resultProperties));
230+
} else {
231+
beans.add(bean);
232+
}
233+
}
234+
return tsModel.setBeans(beans);
235+
}
236+
237+
private static Map<String, TsType> getInheritedProperties(SymbolTable symbolTable, TsModel tsModel, List<TsType> parents) {
238+
final Map<String, TsType> properties = new LinkedHashMap<>();
239+
for (TsType parentType : parents) {
240+
final TsBeanModel parent = tsModel.getBean(getOriginClass(symbolTable, parentType));
241+
if (parent != null) {
242+
properties.putAll(getInheritedProperties(symbolTable, tsModel, parent.getParentAndInterfaces()));
243+
for (TsPropertyModel property : parent.getProperties()) {
244+
properties.put(property.getName(), property.getTsType());
245+
}
246+
}
247+
}
248+
return properties;
249+
}
250+
251+
private static List<TsPropertyModel> getImplementedProperties(SymbolTable symbolTable, TsModel tsModel, List<TsType> interfaces) {
252+
final List<TsPropertyModel> properties = new ArrayList<>();
253+
for (TsType aInterface : interfaces) {
254+
final TsBeanModel bean = tsModel.getBean(getOriginClass(symbolTable, aInterface));
255+
if (bean != null) {
256+
properties.addAll(getImplementedProperties(symbolTable, tsModel, bean.getInterfaces()));
257+
properties.addAll(bean.getProperties());
258+
}
259+
}
260+
return properties;
261+
}
262+
188263
private TsModel transformDates(SymbolTable symbolTable, TsModel tsModel) {
189264
final TsAliasModel dateAsNumber = new TsAliasModel(new TsType.ReferenceType(symbolTable.getSyntheticSymbol("DateAsNumber")), TsType.Number, null);
190265
final TsAliasModel dateAsString = new TsAliasModel(new TsType.ReferenceType(symbolTable.getSyntheticSymbol("DateAsString")), TsType.String, null);
@@ -228,15 +303,10 @@ private TsModel inlineEnums(final TsModel tsModel, final SymbolTable symbolTable
228303
@Override
229304
public TsType transform(TsType tsType) {
230305
if (tsType instanceof TsType.EnumReferenceType) {
231-
final TsType.ReferenceType reference = (TsType.ReferenceType) tsType;
232-
final Class<?> cls = symbolTable.getSymbolClass(reference.symbol);
233-
if (cls != null) {
234-
for (TsAliasModel alias : tsModel.getTypeAliases()) {
235-
if (alias.getOrigin() == cls) {
236-
inlinedAliases.add(alias);
237-
return alias.getDefinition();
238-
}
239-
}
306+
final TsAliasModel alias = tsModel.getTypeAlias(getOriginClass(symbolTable, tsType));
307+
if (alias != null) {
308+
inlinedAliases.add(alias);
309+
return alias.getDefinition();
240310
}
241311
}
242312
return tsType;
@@ -271,14 +341,11 @@ private TsModel createAndUseTaggedUnions(final SymbolTable symbolTable, TsModel
271341
final TsModel model = transformBeanPropertyTypes(tsModel, new TsType.Transformer() {
272342
@Override
273343
public TsType transform(TsType tsType) {
274-
if (tsType instanceof TsType.ReferenceType) {
275-
final TsType.ReferenceType referenceType = (TsType.ReferenceType) tsType;
276-
if (!(referenceType instanceof TsType.GenericReferenceType)) {
277-
final Class<?> cls = symbolTable.getSymbolClass(referenceType.symbol);
278-
final Symbol unionSymbol = symbolTable.hasSymbol(cls, "Union");
279-
if (unionSymbol != null) {
280-
return new TsType.ReferenceType(unionSymbol);
281-
}
344+
final Class<?> cls = getOriginClass(symbolTable, tsType);
345+
if (cls != null && !(tsType instanceof TsType.GenericReferenceType)) {
346+
final Symbol unionSymbol = symbolTable.hasSymbol(cls, "Union");
347+
if (unionSymbol != null) {
348+
return new TsType.ReferenceType(unionSymbol);
282349
}
283350
}
284351
return tsType;
@@ -287,6 +354,42 @@ public TsType transform(TsType tsType) {
287354
return model.setTypeAliases(new ArrayList<>(typeAliases));
288355
}
289356

357+
private TsModel sortDeclarations(SymbolTable symbolTable, TsModel tsModel) {
358+
final List<TsBeanModel> beans = tsModel.getBeans();
359+
final List<TsAliasModel> aliases = tsModel.getTypeAliases();
360+
final List<TsEnumModel<?>> enums = tsModel.getEnums();
361+
if (settings.sortDeclarations) {
362+
for (TsBeanModel bean : beans) {
363+
Collections.sort(bean.getProperties());
364+
}
365+
}
366+
if (settings.sortDeclarations || settings.sortTypeDeclarations) {
367+
Collections.sort(beans);
368+
Collections.sort(aliases);
369+
Collections.sort(enums);
370+
}
371+
final LinkedHashSet<TsBeanModel> orderedBeans = new LinkedHashSet<>();
372+
for (TsBeanModel bean : beans) {
373+
addOrderedClass(symbolTable, tsModel, bean, orderedBeans);
374+
}
375+
return tsModel
376+
.setBeans(new ArrayList<>(orderedBeans))
377+
.setTypeAliases(aliases)
378+
.setEnums(enums);
379+
}
380+
381+
private static void addOrderedClass(SymbolTable symbolTable, TsModel tsModel, TsBeanModel bean, LinkedHashSet<TsBeanModel> orderedBeans) {
382+
// for classes first add their parents to ordered list
383+
if (bean.isClass() && bean.getParent() != null) {
384+
final TsBeanModel parentBean = tsModel.getBean(getOriginClass(symbolTable, bean.getParent()));
385+
if (parentBean != null) {
386+
addOrderedClass(symbolTable, tsModel, parentBean, orderedBeans);
387+
}
388+
}
389+
// add current bean to the ordered list
390+
orderedBeans.add(bean);
391+
}
392+
290393
private static TsModel transformBeanPropertyTypes(TsModel tsModel, TsType.Transformer transformer) {
291394
final List<TsBeanModel> newBeans = new ArrayList<>();
292395
for (TsBeanModel bean : tsModel.getBeans()) {
@@ -300,4 +403,12 @@ private static TsModel transformBeanPropertyTypes(TsModel tsModel, TsType.Transf
300403
return tsModel.setBeans(newBeans);
301404
}
302405

406+
private static Class<?> getOriginClass(SymbolTable symbolTable, TsType type) {
407+
if (type instanceof TsType.ReferenceType) {
408+
final TsType.ReferenceType referenceType = (TsType.ReferenceType) type;
409+
return symbolTable.getSymbolClass(referenceType.symbol);
410+
}
411+
return null;
412+
}
413+
303414
}

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/emitter/Emitter.java

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ private void emitNamespace(TsModel model) {
102102

103103
private void emitElements(TsModel model, boolean exportKeyword, boolean declareKeyword) {
104104
exportKeyword = exportKeyword || forceExportKeyword;
105-
emitInterfaces(model, exportKeyword);
105+
emitBeans(model, exportKeyword);
106106
emitTypeAliases(model, exportKeyword);
107107
emitNumberEnums(model, exportKeyword, declareKeyword);
108108
for (EmitterExtension emitterExtension : settings.extensions) {
@@ -115,23 +115,18 @@ public void writeIndentedLine(String line) {
115115
}
116116
}
117117

118-
private void emitInterfaces(TsModel model, boolean exportKeyword) {
119-
final List<TsBeanModel> beans = new ArrayList<>(model.getBeans());
120-
if (settings.sortDeclarations || settings.sortTypeDeclarations) {
121-
Collections.sort(beans);
122-
}
123-
for (TsBeanModel bean : beans) {
118+
private void emitBeans(TsModel model, boolean exportKeyword) {
119+
for (TsBeanModel bean : model.getBeans()) {
124120
writeNewLine();
125121
emitComments(bean.getComments());
126-
final List<TsType> parents = bean.getParentAndInterfaces();
127-
final String extendsClause = parents.isEmpty() ? "" : " extends " + Utils.join(parents, ", ");
128-
writeIndentedLine(exportKeyword, "interface " + bean.getName() + extendsClause + " {");
122+
final String declarationType = bean.isClass() ? "class" : "interface";
123+
final List<TsType> extendsList = bean.getExtendsList();
124+
final List<TsType> implementsList = bean.getImplementsList();
125+
final String extendsClause = extendsList.isEmpty() ? "" : " extends " + Utils.join(extendsList, ", ");
126+
final String implementsClause = implementsList.isEmpty() ? "" : " implements " + Utils.join(implementsList, ", ");
127+
writeIndentedLine(exportKeyword, declarationType + " " + bean.getName() + extendsClause + implementsClause + " {");
129128
indent++;
130-
final List<TsPropertyModel> properties = bean.getProperties();
131-
if (settings.sortDeclarations) {
132-
Collections.sort(properties);
133-
}
134-
for (TsPropertyModel property : properties) {
129+
for (TsPropertyModel property : bean.getProperties()) {
135130
emitProperty(property);
136131
}
137132
indent--;
@@ -169,11 +164,7 @@ private static boolean isValidIdentifierName(String name) {
169164
}
170165

171166
private void emitTypeAliases(TsModel model, boolean exportKeyword) {
172-
final ArrayList<TsAliasModel> aliases = new ArrayList<>(model.getTypeAliases());
173-
if (settings.sortDeclarations || settings.sortTypeDeclarations) {
174-
Collections.sort(aliases);
175-
}
176-
for (TsAliasModel alias : aliases) {
167+
for (TsAliasModel alias : model.getTypeAliases()) {
177168
writeNewLine();
178169
emitComments(alias.getComments());
179170
writeIndentedLine(exportKeyword, "type " + alias.getName() + " = " + alias.getDefinition().format(settings) + ";");
@@ -184,9 +175,6 @@ private void emitNumberEnums(TsModel model, boolean exportKeyword, boolean decla
184175
final ArrayList<TsEnumModel<?>> enums = settings.mapEnum == EnumMapping.asNumberBasedEnum && !settings.areDefaultStringEnumsOverriddenByExtension()
185176
? new ArrayList<>(model.getEnums())
186177
: new ArrayList<TsEnumModel<?>>(model.getEnums(EnumKind.NumberBased));
187-
if (settings.sortDeclarations || settings.sortTypeDeclarations) {
188-
Collections.sort(enums);
189-
}
190178
for (TsEnumModel<?> enumModel : enums) {
191179
writeNewLine();
192180
emitComments(enumModel.getComments());

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/emitter/TsBeanModel.java

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,31 @@
22
package cz.habarta.typescript.generator.emitter;
33

44
import cz.habarta.typescript.generator.*;
5+
import cz.habarta.typescript.generator.util.Utils;
56
import java.util.*;
67

78

89
public class TsBeanModel extends TsDeclarationModel {
910

11+
private final boolean isClass;
1012
private final TsType parent;
1113
private final List<Class<?>> taggedUnionClasses;
1214
private final List<TsType> interfaces;
1315
private final List<TsPropertyModel> properties;
1416

15-
public TsBeanModel(TsType name, TsType parent, List<Class<?>> taggedUnionClasses, List<TsType> interfaces, List<TsPropertyModel> properties, List<String> comments) {
16-
this(null, name, parent, taggedUnionClasses, interfaces, properties, comments);
17-
}
18-
19-
public TsBeanModel(Class<?> origin, TsType name, TsType parent, List<Class<?>> taggedUnionClasses, List<TsType> interfaces, List<TsPropertyModel> properties, List<String> comments) {
17+
public TsBeanModel(Class<?> origin, boolean isClass, TsType name, TsType parent, List<Class<?>> taggedUnionClasses, List<TsType> interfaces, List<TsPropertyModel> properties, List<String> comments) {
2018
super(origin, name, comments);
19+
this.isClass = isClass;
2120
this.parent = parent;
2221
this.taggedUnionClasses = taggedUnionClasses;
2322
this.interfaces = interfaces;
2423
this.properties = properties;
2524
}
2625

26+
public boolean isClass() {
27+
return isClass;
28+
}
29+
2730
public TsType getParent() {
2831
return parent;
2932
}
@@ -45,12 +48,24 @@ public List<TsType> getParentAndInterfaces() {
4548
return parents;
4649
}
4750

51+
public List<TsType> getExtendsList() {
52+
return isClass
53+
? Utils.listFromNullable(parent)
54+
: getParentAndInterfaces();
55+
}
56+
57+
public List<TsType> getImplementsList() {
58+
return isClass
59+
? interfaces
60+
: Collections.<TsType>emptyList();
61+
}
62+
4863
public List<TsPropertyModel> getProperties() {
4964
return properties;
5065
}
5166

5267
public TsBeanModel withProperties(List<TsPropertyModel> properties) {
53-
return new TsBeanModel(origin, name, parent, taggedUnionClasses, interfaces, properties, comments);
68+
return new TsBeanModel(origin, isClass, name, parent, taggedUnionClasses, interfaces, properties, comments);
5469
}
5570

5671
}

0 commit comments

Comments
 (0)