Skip to content

Commit b93d8cf

Browse files
authored
82 ts field readonly keyword option (#87)
1 parent ce8c935 commit b93d8cf

14 files changed

Lines changed: 104 additions & 14 deletions

File tree

annotation/src/main/java/online/sharedtype/SharedType.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@
9696
* @author Cause Chung
9797
* @implNote generics type bounds are not supported yet.
9898
*/
99-
// TODO: test user-defined array-like types
10099
@Retention(RetentionPolicy.SOURCE)
101100
@Target({java.lang.annotation.ElementType.TYPE})
102101
@Documented
@@ -165,6 +164,15 @@
165164
*/
166165
String typescriptEnumFormat() default "";
167166

167+
/**
168+
* Whether to mark generated type fields as readonly. Default fallback to global properties.
169+
* @return value can be one of:
170+
* "all" - all fields are readonly
171+
* "acyclic" - only fields of not cyclic-referenced types are readonly
172+
* "none" - no fields are readonly
173+
*/
174+
String typescriptFieldReadonlyType() default "";
175+
168176
/**
169177
* Mark a method as an accessor regardless of its name.
170178
* Getter prefixes are configured in global properties.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package online.sharedtype.it.java8;
2+
3+
import java.util.ArrayList;
4+
5+
final class CustomList extends ArrayList<String> {
6+
private static final long serialVersionUID = 5138328998123758779L;
7+
}

it/java8/src/main/java/online/sharedtype/it/java8/CustomMap.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
import java.util.HashMap;
44

55
final class CustomMap extends HashMap<Integer, String> {
6-
6+
private static final long serialVersionUID = 2346546437868922578L;
77
}

it/java8/src/main/java/online/sharedtype/it/java8/OptionalMethod.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111

1212
@SharedType(
1313
rustMacroTraits = {"PartialEq", "serde::Serialize", "serde::Deserialize"},
14-
typescriptOptionalFieldFormat = {"undefined", "null"}
14+
typescriptOptionalFieldFormat = {"undefined", "null"},
15+
typescriptFieldReadonlyType = "none"
1516
)
1617
@Setter
1718
public class OptionalMethod {

processor/src/main/java/online/sharedtype/processor/context/Config.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,22 @@
1212
import java.util.EnumSet;
1313
import java.util.List;
1414
import java.util.Set;
15-
import java.util.function.Function;
1615

1716
/**
1817
* Config wrappers.
1918
*
2019
* @author Cause Chung
2120
*/
21+
@Getter
2222
public final class Config {
23-
@Getter
2423
private final SharedType anno;
25-
@Getter
2624
private final String simpleName;
27-
@Getter
2825
private final String qualifiedName;
2926
private final Set<SharedType.ComponentType> includedComponentTypes;
30-
@Getter
3127
private final boolean constantNamespaced;
32-
@Getter
3328
private final Set<Props.Typescript.OptionalFieldFormat> typescriptOptionalFieldFormats;
34-
@Getter
3529
private final Props.Typescript.EnumFormat typescriptEnumFormat;
30+
private final Props.Typescript.FieldReadonlyType typescriptFieldReadonly;
3631

3732
@Retention(RetentionPolicy.RUNTIME)
3833
@interface AnnoContainer {
@@ -54,6 +49,7 @@ public Config(TypeElement typeElement, Context ctx) {
5449
constantNamespaced = evaluateOptionalBool(anno.constantNamespaced(), ctx.getProps().isConstantNamespaced());
5550
typescriptOptionalFieldFormats = parseTsOptionalFieldFormats(anno, ctx);
5651
typescriptEnumFormat = parseTsEnumFormat(anno, ctx);
52+
typescriptFieldReadonly = parseTsFieldReadonlyType(anno, ctx);
5753
}
5854

5955
public boolean includes(SharedType.ComponentType componentType) {
@@ -95,4 +91,16 @@ private static Props.Typescript.EnumFormat parseTsEnumFormat(SharedType anno, Co
9591
}
9692
return ctx.getProps().getTypescript().getEnumFormat();
9793
}
94+
95+
private static Props.Typescript.FieldReadonlyType parseTsFieldReadonlyType(SharedType anno, Context ctx) {
96+
if (anno.typescriptFieldReadonlyType() != null && !anno.typescriptFieldReadonlyType().isEmpty()) {
97+
try {
98+
return Props.Typescript.FieldReadonlyType.fromString(anno.typescriptFieldReadonlyType());
99+
} catch (IllegalArgumentException e) {
100+
throw new SharedTypeException(String.format(
101+
"Invalid value for SharedType.typescriptFieldReadonlyType: '%s', only 'all', 'acyclic', 'none' is allowed.", anno.typescriptFieldReadonlyType()), e);
102+
}
103+
}
104+
return ctx.getProps().getTypescript().getFieldReadonlyType();
105+
}
98106
}

processor/src/main/java/online/sharedtype/processor/context/Props.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public static final class Typescript {
3636
private final String javaObjectMapType;
3737
private final Set<OptionalFieldFormat> optionalFieldFormats;
3838
private final EnumFormat enumFormat;
39+
private final FieldReadonlyType fieldReadonlyType;
3940

4041
@Getter
4142
public enum OptionalFieldFormat {
@@ -64,6 +65,14 @@ public static EnumFormat fromString(String value) {
6465
return EnumFormat.valueOf(value.toUpperCase());
6566
}
6667
}
68+
69+
public enum FieldReadonlyType {
70+
ALL, ACYCLIC, NONE,
71+
;
72+
public static FieldReadonlyType fromString(String value) {
73+
return FieldReadonlyType.valueOf(value.toUpperCase());
74+
}
75+
}
6776
}
6877

6978
@Builder(access = AccessLevel.PACKAGE)

processor/src/main/java/online/sharedtype/processor/context/PropsFactory.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ private static Props loadProps(Properties properties) {
5656
.optionalFieldFormats(parseEnumSet(properties, "sharedtype.typescript.optional-field-format",
5757
Props.Typescript.OptionalFieldFormat.class, Props.Typescript.OptionalFieldFormat::fromString))
5858
.enumFormat(parseEnum(properties, "sharedtype.typescript.enum-format", Props.Typescript.EnumFormat::fromString))
59+
.fieldReadonlyType(parseEnum(properties, "sharedtype.typescript.field-readonly-type", Props.Typescript.FieldReadonlyType::fromString))
5960
.build())
6061
.rust(Props.Rust.builder()
6162
.outputFileName(properties.getProperty("sharedtype.rust.output-file-name"))
@@ -95,7 +96,7 @@ private static boolean parseBoolean(Properties properties, String key) {
9596
if ("false".equals(value)) {
9697
return false;
9798
}
98-
throw new IllegalArgumentException(String.format("property '%s', can only be 'true' or 'false'.", key));
99+
throw new IllegalArgumentException(String.format("property '%s', can only be 'true' or 'false', but it is '%s'.", key, value));
99100
}
100101

101102
private static <T> Set<Class<? extends T>> parseClassSet(Properties properties, String propertyName) {

processor/src/main/java/online/sharedtype/processor/writer/converter/ConversionUtils.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import online.sharedtype.processor.domain.ConcreteTypeInfo;
44
import online.sharedtype.processor.domain.FieldComponentInfo;
5+
import online.sharedtype.processor.domain.TypeDef;
56

67
import java.util.regex.Pattern;
78

@@ -25,6 +26,10 @@ static boolean isOptionalField(FieldComponentInfo field) {
2526
if (field.optional()) {
2627
return true;
2728
}
29+
return isOfCyclicReferencedType(field);
30+
}
31+
32+
static boolean isOfCyclicReferencedType(FieldComponentInfo field) {
2833
if (field.type() instanceof ConcreteTypeInfo) {
2934
ConcreteTypeInfo type = (ConcreteTypeInfo) field.type();
3035
return type.typeDef() != null && type.typeDef().isCyclicReferenced();

processor/src/main/java/online/sharedtype/processor/writer/converter/TypescriptInterfaceConverter.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
import lombok.RequiredArgsConstructor;
44
import online.sharedtype.processor.context.Config;
55
import online.sharedtype.processor.context.Context;
6+
import online.sharedtype.processor.context.Props;
67
import online.sharedtype.processor.domain.ClassDef;
78
import online.sharedtype.processor.domain.FieldComponentInfo;
89
import online.sharedtype.processor.domain.TypeDef;
10+
import online.sharedtype.processor.support.exception.SharedTypeInternalError;
911
import online.sharedtype.processor.support.utils.Tuple;
1012
import online.sharedtype.processor.writer.converter.type.TypeExpressionConverter;
1113
import online.sharedtype.processor.writer.render.Template;
@@ -62,10 +64,24 @@ private PropertyExpr toPropertyExpr(FieldComponentInfo field, TypeDef contextTyp
6264
interfacePropertyDelimiter,
6365
isFieldOptional && config.getTypescriptOptionalFieldFormats().contains(QUESTION_MARK),
6466
isFieldOptional && config.getTypescriptOptionalFieldFormats().contains(NULL),
65-
isFieldOptional && config.getTypescriptOptionalFieldFormats().contains(UNDEFINED)
67+
isFieldOptional && config.getTypescriptOptionalFieldFormats().contains(UNDEFINED),
68+
isFieldReadonly(field, config)
6669
);
6770
}
6871

72+
private static boolean isFieldReadonly(FieldComponentInfo field, Config config) {
73+
if (config.getTypescriptFieldReadonly() == Props.Typescript.FieldReadonlyType.NONE) {
74+
return false;
75+
}
76+
if (config.getTypescriptFieldReadonly() == Props.Typescript.FieldReadonlyType.ALL) {
77+
return true;
78+
}
79+
if (config.getTypescriptFieldReadonly() == Props.Typescript.FieldReadonlyType.ACYCLIC) {
80+
return !ConversionUtils.isOfCyclicReferencedType(field);
81+
}
82+
throw new SharedTypeInternalError("Unknown typescriptFieldReadonlyType: " + config.getTypescriptFieldReadonly());
83+
}
84+
6985
@RequiredArgsConstructor
7086
@SuppressWarnings("unused")
7187
static final class InterfaceExpr {
@@ -98,5 +114,6 @@ static final class PropertyExpr{
98114
final boolean optional;
99115
final boolean unionNull;
100116
final boolean unionUndefined;
117+
final boolean readonly;
101118
}
102119
}

processor/src/main/resources/sharedtype-default.properties

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ sharedtype.typescript.optional-field-format=?
5454
## enum format, can be "const_enum" (const enum) or "union" (union type).
5555
sharedtype.typescript.enum-format=union
5656

57+
## whether to mark field as readonly, value can be one of:
58+
## "all" - all fields are readonly
59+
## "acyclic" - only fields of not cyclic referenced types are readonly
60+
## "none" - no fields are readonly
61+
sharedtype.typescript.field-readonly-type=acyclic
62+
5763
############################
5864
# Rust specific properties #
5965
############################

0 commit comments

Comments
 (0)