Skip to content

Commit 5a2455a

Browse files
authored
ISSUE-489: Do not create constructors for Typescript interfaces (#490)
* ISSUE-489: Do not create constructors for Typescript interfaces * Fix JsonDeserializationExtension with constructor generation Co-authored-by: Michael Anstis <[email protected]>
1 parent 86cd56b commit 5a2455a

File tree

5 files changed

+346
-8
lines changed

5 files changed

+346
-8
lines changed

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

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -519,13 +519,17 @@ private TsModel addConstructors(SymbolTable symbolTable, TsModel tsModel) {
519519
)));
520520
}
521521
}
522-
final TsConstructorModel constructor = new TsConstructorModel(
523-
TsModifierFlags.None,
524-
Arrays.asList(new TsParameterModel("data", dataType)),
525-
body,
526-
/*comments*/ null
527-
);
528-
beans.add(bean.withConstructor(constructor));
522+
if (bean.isClass()) {
523+
final TsConstructorModel constructor = new TsConstructorModel(
524+
TsModifierFlags.None,
525+
Arrays.asList(new TsParameterModel("data", dataType)),
526+
body,
527+
/*comments*/ null
528+
);
529+
beans.add(bean.withConstructor(constructor));
530+
} else {
531+
beans.add(bean);
532+
}
529533
}
530534
return tsModel.withBeans(beans);
531535
}

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/ext/JsonDeserializationExtension.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import cz.habarta.typescript.generator.emitter.TsBinaryExpression;
1616
import cz.habarta.typescript.generator.emitter.TsBinaryOperator;
1717
import cz.habarta.typescript.generator.emitter.TsCallExpression;
18+
import cz.habarta.typescript.generator.emitter.TsConstructorModel;
1819
import cz.habarta.typescript.generator.emitter.TsExpression;
1920
import cz.habarta.typescript.generator.emitter.TsExpressionStatement;
2021
import cz.habarta.typescript.generator.emitter.TsHelper;
@@ -135,7 +136,7 @@ private static TsMethodModel createDeserializationMethod(SymbolTable symbolTable
135136
new TsBinaryExpression(
136137
new TsIdentifierReference("target"),
137138
TsBinaryOperator.BarBar,
138-
new TsNewExpression(new TsTypeReferenceExpression(new TsType.ReferenceType(beanIdentifier)), typeParameters, null)
139+
new TsNewExpression(new TsTypeReferenceExpression(new TsType.ReferenceType(beanIdentifier)), typeParameters, getConstructorParameters(bean))
139140
)
140141
));
141142
if (bean.getParent() != null) {
@@ -169,6 +170,18 @@ private static TsMethodModel createDeserializationMethod(SymbolTable symbolTable
169170
);
170171
}
171172

173+
private static List<TsIdentifierReference> getConstructorParameters(TsBeanModel bean) {
174+
TsConstructorModel constructor = bean.getConstructor();
175+
if (constructor == null) {
176+
return null;
177+
}
178+
List<TsIdentifierReference> parameters = new ArrayList<>();
179+
for (TsParameterModel parameter : constructor.getParameters()) {
180+
parameters.add(new TsIdentifierReference(parameter.name));
181+
}
182+
return parameters;
183+
}
184+
172185
private static TsMethodModel createDeserializationGenericFunctionConstructor(SymbolTable symbolTable, TsModel tsModel, TsBeanModel bean) {
173186
final Symbol beanIdentifier = symbolTable.getSymbol(bean.getOrigin());
174187
List<TsType.GenericVariableType> typeParameters = getTypeParameters(bean.getOrigin());

typescript-generator-core/src/test/java/cz/habarta/typescript/generator/ClassesTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,19 @@ private static class FooBar {
283283
public int bar;
284284
}
285285

286+
@Test
287+
public void testConstructorOnInterface() {
288+
final Settings settings = TestUtils.settings();
289+
settings.outputFileType = TypeScriptFileType.implementationFile;
290+
settings.mapClasses = ClassMapping.asClasses;
291+
settings.generateConstructors = true;
292+
final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(FooBarInterface.class));
293+
Assert.assertFalse(output.contains("constructor"));
294+
}
295+
296+
private interface FooBarInterface {
297+
}
298+
286299
@Test
287300
public void testConstructorWithGenericsAndInheritance() {
288301
final Settings settings = TestUtils.settings();

typescript-generator-core/src/test/java/cz/habarta/typescript/generator/JsonDeserializationTest.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,40 @@ public void test() throws IOException {
5454
Assert.assertEquals(0, notFoundLines.size());
5555
}
5656

57+
@Test
58+
public void testWithConstructors() throws IOException {
59+
final Settings settings = TestUtils.settings();
60+
settings.outputKind = TypeScriptOutputKind.module;
61+
settings.outputFileType = TypeScriptFileType.implementationFile;
62+
settings.mapClasses = ClassMapping.asClasses;
63+
settings.generateConstructors = true;
64+
settings.extensions.add(new JsonDeserializationExtension());
65+
final File actualFile = new File("target/JsonDeserializationTestWithConstructors-actual.ts");
66+
new TypeScriptGenerator(settings).generateTypeScript(Input.from(User.class), Output.to(actualFile));
67+
final List<String> actualLines = Files.readAllLines(actualFile.toPath(), StandardCharsets.UTF_8);
68+
final List<String> expectedLines = Utils.readLines(getClass().getResourceAsStream("JsonDeserializationTestWithConstructors-expected.ts"));
69+
70+
int contentLines = 0;
71+
int foundLines = 0;
72+
final List<String> notFoundLines = new ArrayList<>();
73+
for (String expectedLine : expectedLines) {
74+
if (!expectedLine.isEmpty() || !expectedLine.trim().equals("}")) {
75+
contentLines++;
76+
if (actualLines.contains(expectedLine)) {
77+
foundLines++;
78+
} else {
79+
notFoundLines.add(expectedLine);
80+
}
81+
}
82+
}
83+
System.out.println(String.format("Number of correctly generated content lines: %d/%d (%d%%).", foundLines, contentLines, 100 * foundLines / contentLines));
84+
System.out.println("Following lines were not generated:");
85+
for (String notFoundLine : notFoundLines) {
86+
System.out.println(notFoundLine);
87+
}
88+
Assert.assertEquals(0, notFoundLines.size());
89+
}
90+
5791
@Test
5892
public void jaxrsApplicationClientTest() {
5993
final Settings settings = TestUtils.settings();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
2+
export class User {
3+
name: string;
4+
authentication: Authentication;
5+
childAccount: boolean;
6+
age: number;
7+
address: Address;
8+
addresses: Address[];
9+
taggedAddresses: { [index: string]: Address };
10+
groupedAddresses: { [index: string]: Address[] };
11+
listOfTaggedAddresses: { [index: string]: Address }[];
12+
tags: string[];
13+
mapping: { [index: string]: string };
14+
listOfListOfString: string[][];
15+
orders: PagedList<Order, Authentication>;
16+
allOrders: PagedList<Order, Authentication>[];
17+
shape: ShapeUnion;
18+
shapes: ShapeUnion[];
19+
20+
constructor(data: User) {
21+
this.name = data.name;
22+
this.authentication = data.authentication;
23+
this.childAccount = data.childAccount;
24+
this.age = data.age;
25+
this.address = data.address;
26+
this.addresses = data.addresses;
27+
this.taggedAddresses = data.taggedAddresses;
28+
this.groupedAddresses = data.groupedAddresses;
29+
this.listOfTaggedAddresses = data.listOfTaggedAddresses;
30+
this.tags = data.tags;
31+
this.mapping = data.mapping;
32+
this.listOfListOfString = data.listOfListOfString;
33+
this.orders = data.orders;
34+
this.allOrders = data.allOrders;
35+
this.shape = data.shape;
36+
this.shapes = data.shapes;
37+
}
38+
39+
static fromData(data: User, target?: User): User {
40+
if (!data) {
41+
return data;
42+
}
43+
const instance = target || new User(data);
44+
instance.name = data.name;
45+
instance.authentication = data.authentication;
46+
instance.childAccount = data.childAccount;
47+
instance.age = data.age;
48+
instance.address = Address.fromData(data.address);
49+
instance.addresses = __getCopyArrayFn(Address.fromData)(data.addresses);
50+
instance.taggedAddresses = __getCopyObjectFn(Address.fromData)(data.taggedAddresses);
51+
instance.groupedAddresses = __getCopyObjectFn(__getCopyArrayFn(Address.fromData))(data.groupedAddresses);
52+
instance.listOfTaggedAddresses = __getCopyArrayFn(__getCopyObjectFn(Address.fromData))(data.listOfTaggedAddresses);
53+
instance.tags = __getCopyArrayFn(__identity<string>())(data.tags);
54+
instance.mapping = __getCopyObjectFn(__identity<string>())(data.mapping);
55+
instance.listOfListOfString = __getCopyArrayFn(__getCopyArrayFn(__identity<string>()))(data.listOfListOfString);
56+
instance.orders = PagedList.fromDataFn<Order, Authentication>(Order.fromData, __identity<Authentication>())(data.orders);
57+
instance.allOrders = __getCopyArrayFn(PagedList.fromDataFn<Order, Authentication>(Order.fromData, __identity<Authentication>()))(data.allOrders);
58+
instance.shape = Shape.fromDataUnion(data.shape);
59+
instance.shapes = __getCopyArrayFn(Shape.fromDataUnion)(data.shapes);
60+
return instance;
61+
}
62+
}
63+
64+
export class Address {
65+
street: string;
66+
city: string;
67+
68+
constructor(data: Address) {
69+
this.street = data.street;
70+
this.city = data.city;
71+
}
72+
73+
static fromData(data: Address, target?: Address): Address {
74+
if (!data) {
75+
return data;
76+
}
77+
const instance = target || new Address(data);
78+
instance.street = data.street;
79+
instance.city = data.city;
80+
return instance;
81+
}
82+
}
83+
84+
export class PagedList<T, A> {
85+
page: number;
86+
items: T[];
87+
additionalInfo: A;
88+
89+
constructor(data: PagedList<T, A>) {
90+
this.page = data.page;
91+
this.items = data.items;
92+
this.additionalInfo = data.additionalInfo;
93+
}
94+
95+
static fromDataFn<T, A>(constructorFnOfT: (data: T) => T, constructorFnOfA: (data: A) => A): (data: PagedList<T, A>) => PagedList<T, A> {
96+
return data => PagedList.fromData(data, constructorFnOfT, constructorFnOfA);
97+
}
98+
99+
static fromData<T, A>(data: PagedList<T, A>, constructorFnOfT: (data: T) => T, constructorFnOfA: (data: A) => A, target?: PagedList<T, A>): PagedList<T, A> {
100+
if (!data) {
101+
return data;
102+
}
103+
const instance = target || new PagedList<T, A>(data);
104+
instance.page = data.page;
105+
instance.items = __getCopyArrayFn(constructorFnOfT)(data.items);
106+
instance.additionalInfo = constructorFnOfA(data.additionalInfo);
107+
return instance;
108+
}
109+
}
110+
111+
export class Order {
112+
id: string;
113+
114+
constructor(data: Order) {
115+
this.id = data.id;
116+
}
117+
118+
static fromData(data: Order, target?: Order): Order {
119+
if (!data) {
120+
return data;
121+
}
122+
const instance = target || new Order(data);
123+
instance.id = data.id;
124+
return instance;
125+
}
126+
}
127+
128+
export class Shape {
129+
kind: "square" | "rectangle" | "circle";
130+
metadata: ShapeMetadata;
131+
132+
constructor(data: Shape) {
133+
this.kind = data.kind;
134+
this.metadata = data.metadata;
135+
}
136+
137+
static fromData(data: Shape, target?: Shape): Shape {
138+
if (!data) {
139+
return data;
140+
}
141+
const instance = target || new Shape(data);
142+
instance.kind = data.kind;
143+
instance.metadata = ShapeMetadata.fromData(data.metadata);
144+
return instance;
145+
}
146+
147+
static fromDataUnion(data: ShapeUnion): ShapeUnion {
148+
if (!data) {
149+
return data;
150+
}
151+
switch (data.kind) {
152+
case "square":
153+
return Square.fromData(data);
154+
case "rectangle":
155+
return Rectangle.fromData(data);
156+
case "circle":
157+
return Circle.fromData(data);
158+
}
159+
}
160+
}
161+
162+
export class ShapeMetadata {
163+
group: string;
164+
165+
constructor(data: ShapeMetadata) {
166+
this.group = data.group;
167+
}
168+
169+
static fromData(data: ShapeMetadata, target?: ShapeMetadata): ShapeMetadata {
170+
if (!data) {
171+
return data;
172+
}
173+
const instance = target || new ShapeMetadata(data);
174+
instance.group = data.group;
175+
return instance;
176+
}
177+
}
178+
179+
export class Square extends Shape {
180+
kind: "square";
181+
size: number;
182+
183+
constructor(data: Square) {
184+
super(data);
185+
this.size = data.size;
186+
}
187+
188+
static fromData(data: Square, target?: Square): Square {
189+
if (!data) {
190+
return data;
191+
}
192+
const instance = target || new Square(data);
193+
super.fromData(data, instance);
194+
instance.size = data.size;
195+
return instance;
196+
}
197+
}
198+
199+
export class Rectangle extends Shape {
200+
kind: "rectangle";
201+
width: number;
202+
height: number;
203+
204+
constructor(data: Rectangle) {
205+
super(data);
206+
this.width = data.width;
207+
this.height = data.height;
208+
}
209+
210+
static fromData(data: Rectangle, target?: Rectangle): Rectangle {
211+
if (!data) {
212+
return data;
213+
}
214+
const instance = target || new Rectangle(data);
215+
super.fromData(data, instance);
216+
instance.width = data.width;
217+
instance.height = data.height;
218+
return instance;
219+
}
220+
}
221+
222+
export class Circle extends Shape {
223+
kind: "circle";
224+
radius: number;
225+
226+
constructor(data: Circle) {
227+
super(data);
228+
this.radius = data.radius;
229+
}
230+
231+
static fromData(data: Circle, target?: Circle): Circle {
232+
if (!data) {
233+
return data;
234+
}
235+
const instance = target || new Circle(data);
236+
super.fromData(data, instance);
237+
instance.radius = data.radius;
238+
return instance;
239+
}
240+
}
241+
242+
export type Authentication = "Password" | "Token" | "Fingerprint" | "Voice";
243+
244+
export type ShapeUnion = Square | Rectangle | Circle;
245+
246+
function __getCopyArrayFn<T>(itemCopyFn: (item: T) => T): (array: T[]) => T[] {
247+
return (array: T[]) => __copyArray(array, itemCopyFn);
248+
}
249+
250+
function __copyArray<T>(array: T[], itemCopyFn: (item: T) => T): T[] {
251+
return array && array.map(item => item && itemCopyFn(item));
252+
}
253+
254+
function __getCopyObjectFn<T>(itemCopyFn: (item: T) => T): (object: { [index: string]: T }) => { [index: string]: T } {
255+
return (object: { [index: string]: T }) => __copyObject(object, itemCopyFn);
256+
}
257+
258+
function __copyObject<T>(object: { [index: string]: T }, itemCopyFn: (item: T) => T): { [index: string]: T } {
259+
if (!object) {
260+
return object;
261+
}
262+
const result: any = {};
263+
for (const key in object) {
264+
if (object.hasOwnProperty(key)) {
265+
const value = object[key];
266+
result[key] = value && itemCopyFn(value);
267+
}
268+
}
269+
return result;
270+
}
271+
272+
function __identity<T>(): (value: T) => T {
273+
return value => value;
274+
}

0 commit comments

Comments
 (0)