Skip to content

Commit d35af81

Browse files
Merge pull request #144 from vojtechhabarta/string-enums
Generate TypeScript 2.4 string enums from Java enums
2 parents 22b2859 + 9d26491 commit d35af81

File tree

23 files changed

+235
-123
lines changed

23 files changed

+235
-123
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
package cz.habarta.typescript.generator;
3+
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
7+
8+
@Retention(RetentionPolicy.RUNTIME)
9+
public @interface DeprecationText {
10+
String value();
11+
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33

44

55
public enum EnumMapping {
6-
asUnion, asInlineUnion, asNumberBasedEnum
6+
asUnion, asInlineUnion, asEnum, asNumberBasedEnum
77
}

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

+9-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public class Settings {
4343
public Map<String, String> customTypeMappings = new LinkedHashMap<>();
4444
public DateMapping mapDate; // default is DateMapping.asDate
4545
public EnumMapping mapEnum; // default is EnumMapping.asUnion
46+
public boolean nonConstEnums = false;
4647
public ClassMapping mapClasses; // default is ClassMapping.asInterfaces
4748
public boolean disableTaggedUnions = false;
4849
public boolean ignoreSwaggerAnnotations = false;
@@ -66,7 +67,7 @@ public class Settings {
6667
public String npmName = null;
6768
public String npmVersion = null;
6869
public Map<String, String> npmPackageDependencies = new LinkedHashMap<>();
69-
public String typescriptVersion = "2.2.2";
70+
public String typescriptVersion = "^2.4";
7071
public boolean displaySerializerWarning = true;
7172
public boolean disableJackson2ModuleDiscovery = false;
7273
public ClassLoader classLoader = null;
@@ -163,6 +164,10 @@ public void validate() {
163164
}
164165
for (EmitterExtension extension : extensions) {
165166
final String extensionName = extension.getClass().getSimpleName();
167+
final DeprecationText deprecation = extension.getClass().getAnnotation(DeprecationText.class);
168+
if (deprecation != null) {
169+
System.out.println(String.format("Warning: Extension '%s' is deprecated: %s", extensionName, deprecation.value()));
170+
}
166171
final EmitterExtensionFeatures features = extension.getFeatures();
167172
if (features.generatesRuntimeCode && outputFileType != TypeScriptFileType.implementationFile) {
168173
throw new RuntimeException(String.format("Extension '%s' generates runtime code but 'outputFileType' parameter is not set to 'implementationFile'.", extensionName));
@@ -192,6 +197,9 @@ public void validate() {
192197
defaultStringEnumsOverriddenByExtension = true;
193198
}
194199
}
200+
if (nonConstEnums && outputFileType != TypeScriptFileType.implementationFile) {
201+
throw new RuntimeException("Non-const enums can only be used in implementation files but 'outputFileType' parameter is not set to 'implementationFile'.");
202+
}
195203
if (mapClasses == ClassMapping.asClasses && outputFileType != TypeScriptFileType.implementationFile) {
196204
throw new RuntimeException("'mapClasses' parameter is set to 'asClasses' which generates runtime code but 'outputFileType' parameter is not set to 'implementationFile'.");
197205
}

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

+2-6
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,8 @@
22
package cz.habarta.typescript.generator.compiler;
33

44

5-
public final class EnumKind<T> {
5+
public enum EnumKind {
66

7-
public static final EnumKind<String> StringBased = new EnumKind<>();
8-
public static final EnumKind<Number> NumberBased = new EnumKind<>();
9-
10-
private EnumKind() {
11-
}
7+
StringBased, NumberBased
128

139
}

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

+14-6
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,21 @@
44
import java.util.List;
55

66

7-
public class EnumMemberModel<T> {
7+
public class EnumMemberModel {
88

99
private final String propertyName;
10-
private final T enumValue;
10+
private final Object/*String|Number*/ enumValue;
1111
private final List<String> comments;
1212

13-
public EnumMemberModel(String propertyName, T enumValue, List<String> comments) {
13+
public EnumMemberModel(String propertyName, String enumValue, List<String> comments) {
14+
this(propertyName, (Object)enumValue, comments);
15+
}
16+
17+
public EnumMemberModel(String propertyName, Number enumValue, List<String> comments) {
18+
this(propertyName, (Object)enumValue, comments);
19+
}
20+
21+
private EnumMemberModel(String propertyName, Object enumValue, List<String> comments) {
1422
this.propertyName = propertyName;
1523
this.enumValue = enumValue;
1624
this.comments = comments;
@@ -20,16 +28,16 @@ public String getPropertyName() {
2028
return propertyName;
2129
}
2230

23-
public T getEnumValue() {
31+
public Object getEnumValue() {
2432
return enumValue;
2533
}
2634

2735
public List<String> getComments() {
2836
return comments;
2937
}
3038

31-
public EnumMemberModel<T> withComments(List<String> comments) {
32-
return new EnumMemberModel<>(propertyName, enumValue, comments);
39+
public EnumMemberModel withComments(List<String> comments) {
40+
return new EnumMemberModel(propertyName, enumValue, comments);
3341
}
3442

3543
}

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

+42-22
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ public TsModel javaToTypeScript(Model model) {
7676
if (settings.mapEnum == EnumMapping.asInlineUnion) {
7777
tsModel = inlineEnums(tsModel, symbolTable);
7878
}
79+
if (settings.mapEnum == EnumMapping.asNumberBasedEnum) {
80+
tsModel = transformEnumsToNumberBasedEnum(tsModel);
81+
}
7982
}
8083

8184
// tagged unions
@@ -89,7 +92,7 @@ public TsModel javaToTypeScript(Model model) {
8992
public TsType javaToTypeScript(Type type) {
9093
final BeanModel beanModel = new BeanModel(Object.class, Object.class, null, null, null, Collections.<Type>emptyList(),
9194
Collections.singletonList(new PropertyModel("property", type, false, null, null, null)), null);
92-
final Model model = new Model(Collections.singletonList(beanModel), Collections.<EnumModel<?>>emptyList(), null);
95+
final Model model = new Model(Collections.singletonList(beanModel), Collections.<EnumModel>emptyList(), null);
9396
final TsModel tsModel = javaToTypeScript(model);
9497
return tsModel.getBeans().get(0).getProperties().get(0).getTsType();
9598
}
@@ -100,11 +103,16 @@ private TsModel processModel(SymbolTable symbolTable, Model model) {
100103
for (BeanModel bean : model.getBeans()) {
101104
beans.add(processBean(symbolTable, model, children, bean));
102105
}
103-
final List<TsEnumModel<?>> enums = new ArrayList<>();
104-
for (EnumModel<?> enumModel : model.getEnums()) {
105-
enums.add(processEnum(symbolTable, enumModel));
106+
final List<TsEnumModel> enums = new ArrayList<>();
107+
final List<TsEnumModel> stringEnums = new ArrayList<>();
108+
for (EnumModel enumModel : model.getEnums()) {
109+
final TsEnumModel tsEnumModel = processEnum(symbolTable, enumModel);
110+
enums.add(tsEnumModel);
111+
if (tsEnumModel.getKind() == EnumKind.StringBased) {
112+
stringEnums.add(tsEnumModel);
113+
}
106114
}
107-
return new TsModel().setBeans(beans).setEnums(enums);
115+
return new TsModel().withBeans(beans).withEnums(enums).withOriginalStringEnums(stringEnums);
108116
}
109117

110118
private Map<Type, List<BeanModel>> createChildrenMap(Model model) {
@@ -205,7 +213,7 @@ private TsPropertyModel processProperty(SymbolTable symbolTable, BeanModel bean,
205213
return new TsPropertyModel(prefix + property.getName() + suffix, tsType, settings.declarePropertiesAsReadOnly, false, property.getComments());
206214
}
207215

208-
private TsEnumModel<?> processEnum(SymbolTable symbolTable, EnumModel<?> enumModel) {
216+
private TsEnumModel processEnum(SymbolTable symbolTable, EnumModel enumModel) {
209217
final Symbol beanIdentifier = symbolTable.getSymbol(enumModel.getOrigin());
210218
return TsEnumModel.fromEnumModel(beanIdentifier, enumModel);
211219
}
@@ -244,7 +252,7 @@ private TsModel removeInheritedProperties(SymbolTable symbolTable, TsModel tsMod
244252
}
245253
beans.add(bean.withProperties(properties));
246254
}
247-
return tsModel.setBeans(beans);
255+
return tsModel.withBeans(beans);
248256
}
249257

250258
private TsModel addImplementedProperties(SymbolTable symbolTable, TsModel tsModel) {
@@ -273,7 +281,7 @@ private TsModel addImplementedProperties(SymbolTable symbolTable, TsModel tsMode
273281
beans.add(bean);
274282
}
275283
}
276-
return tsModel.setBeans(beans);
284+
return tsModel.withBeans(beans);
277285
}
278286

279287
private static Map<String, TsType> getInheritedProperties(SymbolTable symbolTable, TsModel tsModel, List<TsType> parents) {
@@ -544,20 +552,21 @@ public TsType transform(TsType type) {
544552

545553
}
546554
});
547-
return model.setTypeAliases(new ArrayList<>(typeAliases));
555+
return model.withTypeAliases(new ArrayList<>(typeAliases));
548556
}
549557

550558
private TsModel transformEnumsToUnions(TsModel tsModel) {
559+
final List<TsEnumModel> stringEnums = tsModel.getEnums(EnumKind.StringBased);
551560
final LinkedHashSet<TsAliasModel> typeAliases = new LinkedHashSet<>(tsModel.getTypeAliases());
552-
for (TsEnumModel<String> enumModel : tsModel.getEnums(EnumKind.StringBased)) {
561+
for (TsEnumModel enumModel : stringEnums) {
553562
final List<TsType> values = new ArrayList<>();
554-
for (EnumMemberModel<String> member : enumModel.getMembers()) {
555-
values.add(new TsType.StringLiteralType(member.getEnumValue()));
563+
for (EnumMemberModel member : enumModel.getMembers()) {
564+
values.add(new TsType.StringLiteralType((String) member.getEnumValue()));
556565
}
557566
final TsType union = new TsType.UnionType(values);
558567
typeAliases.add(new TsAliasModel(enumModel.getOrigin(), enumModel.getName(), null, union, enumModel.getComments()));
559568
}
560-
return tsModel.setTypeAliases(new ArrayList<>(typeAliases));
569+
return tsModel.withoutEnums(stringEnums).withTypeAliases(new ArrayList<>(typeAliases));
561570
}
562571

563572
private TsModel inlineEnums(final TsModel tsModel, final SymbolTable symbolTable) {
@@ -575,9 +584,20 @@ public TsType transform(TsType tsType) {
575584
return tsType;
576585
}
577586
});
578-
final ArrayList<TsAliasModel> aliases = new ArrayList<>(tsModel.getTypeAliases());
579-
aliases.removeAll(inlinedAliases);
580-
return newTsModel.setTypeAliases(aliases);
587+
return newTsModel.withoutTypeAliases(new ArrayList<>(inlinedAliases));
588+
}
589+
590+
private TsModel transformEnumsToNumberBasedEnum(TsModel tsModel) {
591+
final List<TsEnumModel> stringEnums = tsModel.getEnums(EnumKind.StringBased);
592+
final LinkedHashSet<TsEnumModel> enums = new LinkedHashSet<>();
593+
for (TsEnumModel enumModel : stringEnums) {
594+
final List<EnumMemberModel> members = new ArrayList<>();
595+
for (EnumMemberModel member : enumModel.getMembers()) {
596+
members.add(new EnumMemberModel(member.getPropertyName(), (Number) null, member.getComments()));
597+
}
598+
enums.add(enumModel.withMembers(members));
599+
}
600+
return tsModel.withoutEnums(stringEnums).withEnums(new ArrayList<>(enums));
581601
}
582602

583603
private TsModel createAndUseTaggedUnions(final SymbolTable symbolTable, TsModel tsModel) {
@@ -612,13 +632,13 @@ public TsType transform(TsType tsType) {
612632
return tsType;
613633
}
614634
});
615-
return model.setTypeAliases(new ArrayList<>(typeAliases));
635+
return model.withTypeAliases(new ArrayList<>(typeAliases));
616636
}
617637

618638
private TsModel sortDeclarations(SymbolTable symbolTable, TsModel tsModel) {
619639
final List<TsBeanModel> beans = tsModel.getBeans();
620640
final List<TsAliasModel> aliases = tsModel.getTypeAliases();
621-
final List<TsEnumModel<?>> enums = tsModel.getEnums();
641+
final List<TsEnumModel> enums = tsModel.getEnums();
622642
if (settings.sortDeclarations) {
623643
for (TsBeanModel bean : beans) {
624644
Collections.sort(bean.getProperties());
@@ -634,9 +654,9 @@ private TsModel sortDeclarations(SymbolTable symbolTable, TsModel tsModel) {
634654
addOrderedClass(symbolTable, tsModel, bean, orderedBeans);
635655
}
636656
return tsModel
637-
.setBeans(new ArrayList<>(orderedBeans))
638-
.setTypeAliases(aliases)
639-
.setEnums(enums);
657+
.withBeans(new ArrayList<>(orderedBeans))
658+
.withTypeAliases(aliases)
659+
.withEnums(enums);
640660
}
641661

642662
private static void addOrderedClass(SymbolTable symbolTable, TsModel tsModel, TsBeanModel bean, LinkedHashSet<TsBeanModel> orderedBeans) {
@@ -671,7 +691,7 @@ private static TsModel transformBeanPropertyTypes(TsModel tsModel, TsType.Transf
671691
}
672692
newBeans.add(bean.withProperties(newProperties).withMethods(newMethods));
673693
}
674-
return tsModel.setBeans(newBeans);
694+
return tsModel.withBeans(newBeans);
675695
}
676696

677697
private static Class<?> getOriginClass(SymbolTable symbolTable, TsType type) {

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

+12-13
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
package cz.habarta.typescript.generator.emitter;
33

44
import cz.habarta.typescript.generator.*;
5-
import cz.habarta.typescript.generator.compiler.EnumKind;
65
import cz.habarta.typescript.generator.compiler.EnumMemberModel;
76
import cz.habarta.typescript.generator.util.Utils;
87
import java.io.*;
@@ -105,7 +104,7 @@ private void emitElements(TsModel model, boolean exportKeyword, boolean declareK
105104
exportKeyword = exportKeyword || forceExportKeyword;
106105
emitBeans(model, exportKeyword, declareKeyword);
107106
emitTypeAliases(model, exportKeyword, declareKeyword);
108-
emitNumberEnums(model, exportKeyword, declareKeyword);
107+
emitLiteralEnums(model, exportKeyword, declareKeyword);
109108
emitHelpers(model);
110109
for (EmitterExtension emitterExtension : settings.extensions) {
111110
writeNewLine();
@@ -127,11 +126,8 @@ private void emitTypeAliases(TsModel model, boolean exportKeyword, boolean decla
127126
}
128127
}
129128

130-
private void emitNumberEnums(TsModel model, boolean exportKeyword, boolean declareKeyword) {
131-
final ArrayList<TsEnumModel<?>> enums = settings.mapEnum == EnumMapping.asNumberBasedEnum && !settings.areDefaultStringEnumsOverriddenByExtension()
132-
? new ArrayList<>(model.getEnums())
133-
: new ArrayList<TsEnumModel<?>>(model.getEnums(EnumKind.NumberBased));
134-
for (TsEnumModel<?> enumModel : enums) {
129+
private void emitLiteralEnums(TsModel model, boolean exportKeyword, boolean declareKeyword) {
130+
for (TsEnumModel enumModel : model.getEnums()) {
135131
emitFullyQualifiedDeclaration(enumModel, exportKeyword, declareKeyword);
136132
}
137133
}
@@ -157,7 +153,7 @@ private void emitDeclaration(TsDeclarationModel declaration, boolean exportKeywo
157153
} else if (declaration instanceof TsAliasModel) {
158154
emitTypeAlias((TsAliasModel) declaration, exportKeyword);
159155
} else if (declaration instanceof TsEnumModel) {
160-
emitNumberEnum((TsEnumModel) declaration, exportKeyword, declareKeyword);
156+
emitLiteralEnum((TsEnumModel) declaration, exportKeyword, declareKeyword);
161157
} else {
162158
throw new RuntimeException("Unknown declaration type: " + declaration.getClass().getName());
163159
}
@@ -266,15 +262,18 @@ private void emitTypeAlias(TsAliasModel alias, boolean exportKeyword) {
266262
writeIndentedLine(exportKeyword, "type " + alias.getName().getSimpleName() + genericParameters + " = " + alias.getDefinition().format(settings) + ";");
267263
}
268264

269-
private void emitNumberEnum(TsEnumModel<?> enumModel, boolean exportKeyword, boolean declareKeyword) {
265+
private void emitLiteralEnum(TsEnumModel enumModel, boolean exportKeyword, boolean declareKeyword) {
270266
writeNewLine();
271267
emitComments(enumModel.getComments());
272-
writeIndentedLine(exportKeyword, (declareKeyword ? "declare " : "") + "const enum " + enumModel.getName().getSimpleName() + " {");
268+
final String declareText = declareKeyword ? "declare " : "";
269+
final String constText = settings.nonConstEnums ? "" : "const ";
270+
writeIndentedLine(exportKeyword, declareText + constText + "enum " + enumModel.getName().getSimpleName() + " {");
273271
indent++;
274-
for (EnumMemberModel<?> member : enumModel.getMembers()) {
272+
for (EnumMemberModel member : enumModel.getMembers()) {
275273
emitComments(member.getComments());
276-
final String initializer = enumModel.getKind() == EnumKind.NumberBased
277-
? " = " + member.getEnumValue()
274+
final Object value = member.getEnumValue();
275+
final String initializer = value != null
276+
? " = " + (value instanceof String ? quote((String) value, settings) : String.valueOf(value))
278277
: "";
279278
writeIndentedLine(member.getPropertyName() + initializer + ",");
280279
}

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

+13-10
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,31 @@
88
import java.util.List;
99

1010

11-
// T extends String | Number
12-
public class TsEnumModel<T> extends TsDeclarationModel {
13-
14-
private final EnumKind<T> kind;
15-
private final List<EnumMemberModel<T>> members;
11+
public class TsEnumModel extends TsDeclarationModel {
1612

17-
public TsEnumModel(Class<?> origin, Symbol name, EnumKind<T> kind, List<EnumMemberModel<T>> members, List<String> comments) {
13+
private final EnumKind kind;
14+
private final List<EnumMemberModel> members;
15+
16+
public TsEnumModel(Class<?> origin, Symbol name, EnumKind kind, List<EnumMemberModel> members, List<String> comments) {
1817
super(origin, null, name, comments);
1918
this.kind = kind;
2019
this.members = members;
2120
}
2221

23-
public static <T> TsEnumModel<T> fromEnumModel(Symbol name, EnumModel<T> enumModel) {
24-
return new TsEnumModel<>(enumModel.getOrigin(), name, enumModel.getKind(), enumModel.getMembers(), enumModel.getComments());
22+
public static TsEnumModel fromEnumModel(Symbol name, EnumModel enumModel) {
23+
return new TsEnumModel(enumModel.getOrigin(), name, enumModel.getKind(), enumModel.getMembers(), enumModel.getComments());
2524
}
2625

27-
public EnumKind<T> getKind() {
26+
public EnumKind getKind() {
2827
return kind;
2928
}
3029

31-
public List<EnumMemberModel<T>> getMembers() {
30+
public List<EnumMemberModel> getMembers() {
3231
return members;
3332
}
3433

34+
public TsEnumModel withMembers(List<EnumMemberModel> members) {
35+
return new TsEnumModel(origin, name, kind, members, comments);
36+
}
37+
3538
}

0 commit comments

Comments
 (0)