lombok-typescript 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -64
- package/dist/cli/index.cjs +389 -98
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.cts +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +389 -98
- package/dist/cli/index.js.map +1 -1
- package/dist/codegen/index.cjs +371 -92
- package/dist/codegen/index.cjs.map +1 -1
- package/dist/codegen/index.d.cts +6 -3
- package/dist/codegen/index.d.ts +6 -3
- package/dist/codegen/index.js +371 -92
- package/dist/codegen/index.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/legacy/index.cjs +195 -0
- package/dist/legacy/index.cjs.map +1 -1
- package/dist/legacy/index.d.cts +24 -3
- package/dist/legacy/index.d.ts +24 -3
- package/dist/legacy/index.js +185 -1
- package/dist/legacy/index.js.map +1 -1
- package/dist/{memoize-DvelzGDl.d.ts → log-Dbe0sSP1.d.ts} +23 -1
- package/dist/{memoize-Bj9pm_cK.d.cts → log-Dpr79VRt.d.cts} +23 -1
- package/dist/stage3/index.cjs +200 -0
- package/dist/stage3/index.cjs.map +1 -1
- package/dist/stage3/index.d.cts +24 -3
- package/dist/stage3/index.d.ts +24 -3
- package/dist/stage3/index.js +190 -1
- package/dist/stage3/index.js.map +1 -1
- package/package.json +4 -17
package/dist/codegen/index.js
CHANGED
|
@@ -69,31 +69,190 @@ function toParameterInfo(param) {
|
|
|
69
69
|
decorators: param.getDecorators().map(toDecoratorInfo)
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
|
+
|
|
73
|
+
// src/decorators/shared/composition.ts
|
|
74
|
+
var CONFLICTING_CLASS_DECORATOR_PAIRS = [
|
|
75
|
+
["Data", "Value"]
|
|
76
|
+
];
|
|
77
|
+
function classHasDecorator(info, name) {
|
|
78
|
+
return info.decorators.some((d) => d.name === name);
|
|
79
|
+
}
|
|
80
|
+
function validateClassComposition(info) {
|
|
81
|
+
for (const [a, b] of CONFLICTING_CLASS_DECORATOR_PAIRS) {
|
|
82
|
+
if (classHasDecorator(info, a) && classHasDecorator(info, b)) {
|
|
83
|
+
throw new Error(`Class "${info.name}": @${a} and @${b} cannot be used together.`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function validateAllClassCompositions(classes) {
|
|
88
|
+
for (const info of classes) {
|
|
89
|
+
validateClassComposition(info);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
72
92
|
function toImportPath(sourcePath, fromDir) {
|
|
73
93
|
let rel = relative(fromDir, sourcePath).replace(/\\/g, "/");
|
|
74
94
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
75
95
|
return rel.replace(/\.tsx?$/u, ".js");
|
|
76
96
|
}
|
|
97
|
+
var CODEGEN_CLASS_DECORATORS = [
|
|
98
|
+
"Builder",
|
|
99
|
+
"Data",
|
|
100
|
+
"ToString",
|
|
101
|
+
"Value",
|
|
102
|
+
"Equals",
|
|
103
|
+
"With"
|
|
104
|
+
];
|
|
77
105
|
function hasCodegenClassDecorator(info) {
|
|
78
|
-
return
|
|
106
|
+
return CODEGEN_CLASS_DECORATORS.some((name) => hasClassDecorator(info, name)) || info.fields.some(
|
|
107
|
+
(f) => fieldHasDecorator(f, "Getter") || fieldHasDecorator(f, "Setter") || fieldHasDecorator(f, "With") || fieldHasDecorator(f, "Delegate")
|
|
108
|
+
);
|
|
79
109
|
}
|
|
80
110
|
function hasClassDecorator(info, name) {
|
|
81
111
|
return info.decorators.some((d) => d.name === name);
|
|
82
112
|
}
|
|
113
|
+
function fieldHasDecorator(field, name) {
|
|
114
|
+
return field.decorators.some((d) => d.name === name);
|
|
115
|
+
}
|
|
83
116
|
function fieldExcludesToString(field) {
|
|
84
117
|
return field.decorators.some(
|
|
85
118
|
(d) => d.name === "ToStringExclude" || d.name === "ToString.Exclude"
|
|
86
119
|
);
|
|
87
120
|
}
|
|
121
|
+
function fieldExcludesEquals(field) {
|
|
122
|
+
return field.decorators.some((d) => d.name === "EqualsExclude" || d.name === "Equals.Exclude");
|
|
123
|
+
}
|
|
88
124
|
function visibleFields(info) {
|
|
89
|
-
if (hasClassDecorator(info, "ToString")) {
|
|
125
|
+
if (hasClassDecorator(info, "ToString") || hasClassDecorator(info, "Data") || hasClassDecorator(info, "Value")) {
|
|
90
126
|
return info.fields.filter((f) => !fieldExcludesToString(f));
|
|
91
127
|
}
|
|
92
128
|
return info.fields;
|
|
93
129
|
}
|
|
130
|
+
function equalsFields(info) {
|
|
131
|
+
return info.fields.filter((f) => !fieldExcludesEquals(f));
|
|
132
|
+
}
|
|
133
|
+
function getterName(fieldName) {
|
|
134
|
+
return `get${fieldName.charAt(0).toUpperCase()}${fieldName.slice(1)}`;
|
|
135
|
+
}
|
|
136
|
+
function setterName(fieldName) {
|
|
137
|
+
return `set${fieldName.charAt(0).toUpperCase()}${fieldName.slice(1)}`;
|
|
138
|
+
}
|
|
139
|
+
function withMethodName(fieldName) {
|
|
140
|
+
return `with${fieldName.charAt(0).toUpperCase()}${fieldName.slice(1)}`;
|
|
141
|
+
}
|
|
94
142
|
function builderClassName(className) {
|
|
95
143
|
return `${className}Builder`;
|
|
96
144
|
}
|
|
145
|
+
function hasDataOrValue(info) {
|
|
146
|
+
return hasClassDecorator(info, "Data") || hasClassDecorator(info, "Value");
|
|
147
|
+
}
|
|
148
|
+
function wantsWithMethods(info, field) {
|
|
149
|
+
return hasClassDecorator(info, "Value") || hasClassDecorator(info, "With") || fieldHasDecorator(field, "With");
|
|
150
|
+
}
|
|
151
|
+
function wantsGetter(info, field) {
|
|
152
|
+
return hasDataOrValue(info) || fieldHasDecorator(field, "Getter");
|
|
153
|
+
}
|
|
154
|
+
function wantsSetter(info, field) {
|
|
155
|
+
if (hasClassDecorator(info, "Value")) return false;
|
|
156
|
+
if (hasClassDecorator(info, "Data")) return !effectiveReadonly(info, field);
|
|
157
|
+
return fieldHasDecorator(field, "Setter");
|
|
158
|
+
}
|
|
159
|
+
function wantsEquals(info) {
|
|
160
|
+
return hasClassDecorator(info, "Data") || hasClassDecorator(info, "Value") || hasClassDecorator(info, "Equals");
|
|
161
|
+
}
|
|
162
|
+
function wantsToString(info) {
|
|
163
|
+
return hasClassDecorator(info, "ToString") || hasClassDecorator(info, "Data") || hasClassDecorator(info, "Value");
|
|
164
|
+
}
|
|
165
|
+
function effectiveReadonly(info, field) {
|
|
166
|
+
if (field.isReadonly) return true;
|
|
167
|
+
const defaults = getFieldDefaultsOptions(info);
|
|
168
|
+
return defaults?.makeFinal === true;
|
|
169
|
+
}
|
|
170
|
+
function getFieldDefaultsOptions(info) {
|
|
171
|
+
const dec = info.decorators.find((d) => d.name === "FieldDefaults");
|
|
172
|
+
if (!dec) return void 0;
|
|
173
|
+
const [first] = dec.arguments;
|
|
174
|
+
if (typeof first === "string" && first.startsWith("{")) {
|
|
175
|
+
try {
|
|
176
|
+
const parsed = JSON.parse(first.replace(/(\w+):/g, '"$1":').replace(/'/g, '"'));
|
|
177
|
+
return {
|
|
178
|
+
level: parsed.level ?? "public",
|
|
179
|
+
makeFinal: parsed.makeFinal ?? false
|
|
180
|
+
};
|
|
181
|
+
} catch {
|
|
182
|
+
return { level: "public", makeFinal: false };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return { level: "public", makeFinal: false };
|
|
186
|
+
}
|
|
187
|
+
function hasFluentAccessors(info) {
|
|
188
|
+
const dec = info.decorators.find((d) => d.name === "Accessors");
|
|
189
|
+
if (!dec) return false;
|
|
190
|
+
const [first] = dec.arguments;
|
|
191
|
+
if (typeof first === "string") {
|
|
192
|
+
return first.includes("chain") || first.includes("fluent");
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
function getDelegateMethods(field) {
|
|
197
|
+
const dec = field.decorators.find((d) => d.name === "Delegate");
|
|
198
|
+
if (!dec || dec.arguments.length === 0) return [];
|
|
199
|
+
if (dec.arguments.length === 1 && String(dec.arguments[0]).startsWith("[")) {
|
|
200
|
+
try {
|
|
201
|
+
return JSON.parse(String(dec.arguments[0]).replace(/'/g, '"'));
|
|
202
|
+
} catch {
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return dec.arguments.map((a) => String(a).replace(/^['"]|['"]$/g, ""));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/codegen/emitters/accessors-emit.ts
|
|
210
|
+
function emitGetterFn(info, field) {
|
|
211
|
+
const g = getterName(field.name);
|
|
212
|
+
return `
|
|
213
|
+
function ${info.name}_${g}(this: ${info.name}): ${field.type} {
|
|
214
|
+
return this.${field.name};
|
|
215
|
+
}`.trim();
|
|
216
|
+
}
|
|
217
|
+
function emitSetterFn(info, field) {
|
|
218
|
+
const s = setterName(field.name);
|
|
219
|
+
const fluent = hasFluentAccessors(info);
|
|
220
|
+
const ret = fluent ? `return this;` : "";
|
|
221
|
+
const returnType = fluent ? info.name : "void";
|
|
222
|
+
return `
|
|
223
|
+
function ${info.name}_${s}(this: ${info.name}, value: ${field.type}): ${returnType} {
|
|
224
|
+
this.${field.name} = value;
|
|
225
|
+
${ret}
|
|
226
|
+
}`.trim();
|
|
227
|
+
}
|
|
228
|
+
function emitAccessorFns(info) {
|
|
229
|
+
const fns = [];
|
|
230
|
+
for (const field of info.fields) {
|
|
231
|
+
if (wantsGetter(info, field)) {
|
|
232
|
+
fns.push(emitGetterFn(info, field));
|
|
233
|
+
}
|
|
234
|
+
if (wantsSetter(info, field)) {
|
|
235
|
+
fns.push(emitSetterFn(info, field));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return fns.join("\n\n");
|
|
239
|
+
}
|
|
240
|
+
function emitAccessorApplyAssignments(info) {
|
|
241
|
+
const assignments = [];
|
|
242
|
+
for (const field of info.fields) {
|
|
243
|
+
if (wantsGetter(info, field)) {
|
|
244
|
+
assignments.push(
|
|
245
|
+
`prototype.${getterName(field.name)} = ${info.name}_${getterName(field.name)};`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
if (wantsSetter(info, field)) {
|
|
249
|
+
assignments.push(
|
|
250
|
+
`prototype.${setterName(field.name)} = ${info.name}_${setterName(field.name)};`
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return assignments;
|
|
255
|
+
}
|
|
97
256
|
|
|
98
257
|
// src/codegen/emitters/builder.ts
|
|
99
258
|
function emitBuilderClass(info) {
|
|
@@ -131,16 +290,8 @@ ${info.fields.map((f) => ` instance.${f.name} = this._${f.name}${f.isOptional
|
|
|
131
290
|
}
|
|
132
291
|
}`.trim();
|
|
133
292
|
}
|
|
134
|
-
function
|
|
135
|
-
const
|
|
136
|
-
const lines = [
|
|
137
|
-
"// Auto-generated type augmentation by lombok-typescript.",
|
|
138
|
-
"// Do not edit. Regenerate via `lombok-ts generate`.",
|
|
139
|
-
"",
|
|
140
|
-
"export {};",
|
|
141
|
-
"",
|
|
142
|
-
`declare module '${relSource}' {`
|
|
143
|
-
];
|
|
293
|
+
function emitDeclarationModuleBlock(relSource, classes) {
|
|
294
|
+
const lines = [`declare module '${relSource}' {`];
|
|
144
295
|
for (const info of classes) {
|
|
145
296
|
if (hasClassDecorator(info, "Builder")) {
|
|
146
297
|
const builderName = builderClassName(info.name);
|
|
@@ -155,24 +306,33 @@ function emitDeclarationShim(sourcePath, companionOutputPath, classes) {
|
|
|
155
306
|
}
|
|
156
307
|
if (hasClassDecorator(info, "Builder")) {
|
|
157
308
|
const builderName = builderClassName(info.name);
|
|
158
|
-
lines.push(`
|
|
159
|
-
lines.push(`
|
|
309
|
+
lines.push(` namespace ${info.name} {`);
|
|
310
|
+
lines.push(` export function builder(): ${builderName};`);
|
|
160
311
|
lines.push(" }");
|
|
161
312
|
lines.push("");
|
|
162
313
|
}
|
|
163
314
|
const augments = [];
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
315
|
+
for (const f of info.fields) {
|
|
316
|
+
if (wantsGetter(info, f)) {
|
|
317
|
+
augments.push(` ${getterName(f.name)}(): ${f.type};`);
|
|
318
|
+
}
|
|
319
|
+
if (wantsSetter(info, f)) {
|
|
320
|
+
const ret = hasClassDecorator(info, "Accessors") ? info.name : "void";
|
|
321
|
+
augments.push(` ${setterName(f.name)}(value: ${f.type}): ${ret};`);
|
|
322
|
+
}
|
|
323
|
+
if (wantsWithMethods(info, f)) {
|
|
324
|
+
augments.push(` ${withMethodName(f.name)}(value: ${f.type}): ${info.name};`);
|
|
325
|
+
}
|
|
326
|
+
if (fieldHasDecorator(f, "Delegate")) {
|
|
327
|
+
for (const method of getDelegateMethods(f)) {
|
|
328
|
+
augments.push(` ${method}(...args: unknown[]): unknown;`);
|
|
171
329
|
}
|
|
172
330
|
}
|
|
331
|
+
}
|
|
332
|
+
if (wantsEquals(info)) {
|
|
173
333
|
augments.push(` equals(other: ${info.name} | null | undefined): boolean;`);
|
|
174
|
-
|
|
175
|
-
|
|
334
|
+
}
|
|
335
|
+
if (wantsToString(info)) {
|
|
176
336
|
augments.push(" toString(): string;");
|
|
177
337
|
}
|
|
178
338
|
if (augments.length > 0) {
|
|
@@ -180,11 +340,99 @@ function emitDeclarationShim(sourcePath, companionOutputPath, classes) {
|
|
|
180
340
|
lines.push(...augments);
|
|
181
341
|
lines.push(" }");
|
|
182
342
|
}
|
|
343
|
+
if (hasClassDecorator(info, "Equals")) {
|
|
344
|
+
lines.push(` namespace ${info.name} {`);
|
|
345
|
+
lines.push(
|
|
346
|
+
` export function equals(a: ${info.name} | null | undefined, b: ${info.name} | null | undefined): boolean;`
|
|
347
|
+
);
|
|
348
|
+
lines.push(" }");
|
|
349
|
+
lines.push("");
|
|
350
|
+
}
|
|
183
351
|
}
|
|
184
352
|
lines.push("}");
|
|
185
|
-
lines.push("");
|
|
186
353
|
return lines.join("\n");
|
|
187
354
|
}
|
|
355
|
+
function emitDeclarationShim(sourcePath, companionOutputPath, classes) {
|
|
356
|
+
const relSource = toImportPath(sourcePath, dirname(companionOutputPath));
|
|
357
|
+
const moduleBlock = emitDeclarationModuleBlock(relSource, classes);
|
|
358
|
+
return [
|
|
359
|
+
"// Auto-generated type augmentation by lombok-typescript.",
|
|
360
|
+
"// Do not edit. Regenerate via `lombok-ts generate`.",
|
|
361
|
+
"",
|
|
362
|
+
"export {};",
|
|
363
|
+
"",
|
|
364
|
+
moduleBlock,
|
|
365
|
+
""
|
|
366
|
+
].join("\n");
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// src/codegen/emitters/delegate-emit.ts
|
|
370
|
+
function emitDelegateFns(info) {
|
|
371
|
+
const fns = [];
|
|
372
|
+
for (const field of info.fields) {
|
|
373
|
+
if (!fieldHasDecorator(field, "Delegate")) continue;
|
|
374
|
+
const methods = getDelegateMethods(field);
|
|
375
|
+
for (const method of methods) {
|
|
376
|
+
fns.push(
|
|
377
|
+
`
|
|
378
|
+
function ${info.name}_${method}(this: ${info.name}, ...args: unknown[]): unknown {
|
|
379
|
+
const target = this.${field.name} as { ${method}: (...a: unknown[]) => unknown };
|
|
380
|
+
return target.${method}(...args);
|
|
381
|
+
}`.trim()
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return fns.join("\n\n");
|
|
386
|
+
}
|
|
387
|
+
function emitDelegateApplyAssignments(info) {
|
|
388
|
+
const assignments = [];
|
|
389
|
+
for (const field of info.fields) {
|
|
390
|
+
if (!fieldHasDecorator(field, "Delegate")) continue;
|
|
391
|
+
for (const method of getDelegateMethods(field)) {
|
|
392
|
+
assignments.push(`prototype.${method} = ${info.name}_${method};`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return assignments;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// src/codegen/emitters/equals-emit.ts
|
|
399
|
+
function emitEqualsFn(info) {
|
|
400
|
+
if (!wantsEquals(info)) return "";
|
|
401
|
+
const fields = equalsFields(info);
|
|
402
|
+
const checks = fields.map((f) => `this.${f.name} === other.${f.name}`).join(" &&\n ");
|
|
403
|
+
return `
|
|
404
|
+
function ${info.name}_equals(this: ${info.name}, other: ${info.name} | null | undefined): boolean {
|
|
405
|
+
if (other === null || other === undefined) return false;
|
|
406
|
+
if (!(other instanceof (this.constructor as typeof ${info.name}))) return false;
|
|
407
|
+
return ${checks || "true"};
|
|
408
|
+
}`.trim();
|
|
409
|
+
}
|
|
410
|
+
function emitEqualsStaticFn(info) {
|
|
411
|
+
if (!hasClassDecorator(info, "Equals")) return "";
|
|
412
|
+
return `
|
|
413
|
+
function ${info.name}_equalsStatic(a: ${info.name} | null | undefined, b: ${info.name} | null | undefined): boolean {
|
|
414
|
+
if (a === b) return true;
|
|
415
|
+
if (a === null || a === undefined || b === null || b === undefined) return false;
|
|
416
|
+
return a.equals(b);
|
|
417
|
+
}`.trim();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// src/codegen/emitters/with-emit.ts
|
|
421
|
+
function emitSingleWithFn(info, field) {
|
|
422
|
+
const method = withMethodName(field.name);
|
|
423
|
+
return `
|
|
424
|
+
function ${info.name}_${method}(this: ${info.name}, value: ${field.type}): ${info.name} {
|
|
425
|
+
const copy = Object.create(Object.getPrototypeOf(this)) as ${info.name};
|
|
426
|
+
Object.assign(copy, this);
|
|
427
|
+
copy.${field.name} = value;
|
|
428
|
+
return copy;
|
|
429
|
+
}`.trim();
|
|
430
|
+
}
|
|
431
|
+
function emitWithFns(info) {
|
|
432
|
+
const fields = info.fields.filter((f) => wantsWithMethods(info, f));
|
|
433
|
+
if (fields.length === 0) return "";
|
|
434
|
+
return fields.map((f) => emitSingleWithFn(info, f)).join("\n\n");
|
|
435
|
+
}
|
|
188
436
|
|
|
189
437
|
// src/codegen/emitters/index.ts
|
|
190
438
|
function emitImports(classes, importPath) {
|
|
@@ -195,7 +443,7 @@ function emitImports(classes, importPath) {
|
|
|
195
443
|
`;
|
|
196
444
|
}
|
|
197
445
|
function emitToStringFn(info) {
|
|
198
|
-
if (!
|
|
446
|
+
if (!wantsToString(info)) return "";
|
|
199
447
|
const fields = visibleFields(info);
|
|
200
448
|
const parts = fields.map((f) => `${f.name}=\${String(this.${f.name})}`).join(", ");
|
|
201
449
|
return `
|
|
@@ -205,26 +453,32 @@ function ${info.name}_toString(this: ${info.name}): string {
|
|
|
205
453
|
}
|
|
206
454
|
function emitApplyMixin(info) {
|
|
207
455
|
const assignments = [];
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
assignments.push(`prototype.${g} = ${info.name}_${g};`);
|
|
212
|
-
if (!f.isReadonly) {
|
|
213
|
-
const s = `set${f.name.charAt(0).toUpperCase()}${f.name.slice(1)}`;
|
|
214
|
-
assignments.push(`prototype.${s} = ${info.name}_${s};`);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
456
|
+
assignments.push(...emitAccessorApplyAssignments(info));
|
|
457
|
+
assignments.push(...emitDelegateApplyAssignments(info));
|
|
458
|
+
if (wantsEquals(info)) {
|
|
217
459
|
assignments.push(`prototype.equals = ${info.name}_equals;`);
|
|
218
|
-
|
|
219
|
-
|
|
460
|
+
}
|
|
461
|
+
if (wantsToString(info)) {
|
|
220
462
|
assignments.push(`prototype.toString = ${info.name}_toString;`);
|
|
221
463
|
}
|
|
464
|
+
for (const field of info.fields) {
|
|
465
|
+
if (wantsWithMethods(info, field)) {
|
|
466
|
+
assignments.push(
|
|
467
|
+
`prototype.${withMethodName(field.name)} = ${info.name}_${withMethodName(field.name)};`
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
222
471
|
if (hasClassDecorator(info, "Builder")) {
|
|
223
472
|
const bName = builderClassName(info.name);
|
|
224
473
|
assignments.push(
|
|
225
474
|
`(ctor as typeof ${info.name} & { builder(): ${bName} }).builder = ${info.name}_builder;`
|
|
226
475
|
);
|
|
227
476
|
}
|
|
477
|
+
if (hasClassDecorator(info, "Equals")) {
|
|
478
|
+
assignments.push(
|
|
479
|
+
`(ctor as typeof ${info.name} & { equals(a: ${info.name} | null | undefined, b: ${info.name} | null | undefined): boolean }).equals = ${info.name}_equalsStatic;`
|
|
480
|
+
);
|
|
481
|
+
}
|
|
228
482
|
if (assignments.length === 0) return "";
|
|
229
483
|
return `
|
|
230
484
|
export function apply${info.name}Generated(ctor: typeof ${info.name}): void {
|
|
@@ -232,39 +486,6 @@ export function apply${info.name}Generated(ctor: typeof ${info.name}): void {
|
|
|
232
486
|
${assignments.join("\n ")}
|
|
233
487
|
}`.trim();
|
|
234
488
|
}
|
|
235
|
-
function emitDataFns(info) {
|
|
236
|
-
if (!hasClassDecorator(info, "Data")) return "";
|
|
237
|
-
const fns = [];
|
|
238
|
-
for (const f of info.fields) {
|
|
239
|
-
const g = `get${f.name.charAt(0).toUpperCase()}${f.name.slice(1)}`;
|
|
240
|
-
fns.push(
|
|
241
|
-
`
|
|
242
|
-
function ${info.name}_${g}(this: ${info.name}): ${f.type} {
|
|
243
|
-
return this.${f.name};
|
|
244
|
-
}`.trim()
|
|
245
|
-
);
|
|
246
|
-
if (!f.isReadonly) {
|
|
247
|
-
const s = `set${f.name.charAt(0).toUpperCase()}${f.name.slice(1)}`;
|
|
248
|
-
fns.push(
|
|
249
|
-
`
|
|
250
|
-
function ${info.name}_${s}(this: ${info.name}, value: ${f.type}): void {
|
|
251
|
-
this.${f.name} = value;
|
|
252
|
-
}`.trim()
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
const equalsBody = info.fields.map((f) => `this.${f.name} === other.${f.name}`).join(" &&\n ");
|
|
257
|
-
fns.push(
|
|
258
|
-
`
|
|
259
|
-
function ${info.name}_equals(this: ${info.name}, other: ${info.name} | null | undefined): boolean {
|
|
260
|
-
if (other === null || other === undefined) return false;
|
|
261
|
-
if (!(other instanceof (this.constructor as typeof ${info.name}))) return false;
|
|
262
|
-
return ${equalsBody || "true"};
|
|
263
|
-
}`.trim()
|
|
264
|
-
);
|
|
265
|
-
fns.push(emitToStringFn(info));
|
|
266
|
-
return fns.filter(Boolean).join("\n\n");
|
|
267
|
-
}
|
|
268
489
|
function emitBuilderFn(info) {
|
|
269
490
|
if (!hasClassDecorator(info, "Builder")) return "";
|
|
270
491
|
const bName = builderClassName(info.name);
|
|
@@ -273,7 +494,30 @@ function ${info.name}_builder(): ${bName} {
|
|
|
273
494
|
return ${bName}.builder();
|
|
274
495
|
}`.trim();
|
|
275
496
|
}
|
|
497
|
+
function emitClassCompanionBlocks(info) {
|
|
498
|
+
const blocks = [];
|
|
499
|
+
const builder = emitBuilderClass(info);
|
|
500
|
+
if (builder) blocks.push(builder);
|
|
501
|
+
const accessors = emitAccessorFns(info);
|
|
502
|
+
if (accessors) blocks.push(accessors);
|
|
503
|
+
const withFns = emitWithFns(info);
|
|
504
|
+
if (withFns) blocks.push(withFns);
|
|
505
|
+
const equalsFn = emitEqualsFn(info);
|
|
506
|
+
if (equalsFn) blocks.push(equalsFn);
|
|
507
|
+
const equalsStatic = emitEqualsStaticFn(info);
|
|
508
|
+
if (equalsStatic) blocks.push(equalsStatic);
|
|
509
|
+
const toString = emitToStringFn(info);
|
|
510
|
+
if (toString) blocks.push(toString);
|
|
511
|
+
const delegate = emitDelegateFns(info);
|
|
512
|
+
if (delegate) blocks.push(delegate);
|
|
513
|
+
const builderFn = emitBuilderFn(info);
|
|
514
|
+
if (builderFn) blocks.push(builderFn);
|
|
515
|
+
const apply = emitApplyMixin(info);
|
|
516
|
+
if (apply) blocks.push(apply);
|
|
517
|
+
return blocks.filter(Boolean).join("\n\n");
|
|
518
|
+
}
|
|
276
519
|
function emitCompanionFile(sourcePath, companionOutputPath, classes, cwd) {
|
|
520
|
+
validateAllClassCompositions(classes);
|
|
277
521
|
const header = [
|
|
278
522
|
"// Auto-generated by lombok-typescript.",
|
|
279
523
|
"// Source: " + relative(cwd, sourcePath).replace(/\\/g, "/"),
|
|
@@ -283,25 +527,16 @@ function emitCompanionFile(sourcePath, companionOutputPath, classes, cwd) {
|
|
|
283
527
|
const importPath = toImportPath(sourcePath, dirname(companionOutputPath));
|
|
284
528
|
const blocks = [];
|
|
285
529
|
for (const info of classes) {
|
|
286
|
-
const
|
|
287
|
-
if (
|
|
288
|
-
if (hasClassDecorator(info, "Data")) {
|
|
289
|
-
blocks.push(emitDataFns(info));
|
|
290
|
-
} else {
|
|
291
|
-
const ts2 = emitToStringFn(info);
|
|
292
|
-
if (ts2) blocks.push(ts2);
|
|
293
|
-
}
|
|
294
|
-
const builderFn = emitBuilderFn(info);
|
|
295
|
-
if (builderFn) blocks.push(builderFn);
|
|
296
|
-
const apply = emitApplyMixin(info);
|
|
297
|
-
if (apply) blocks.push(apply);
|
|
530
|
+
const chunk = emitClassCompanionBlocks(info);
|
|
531
|
+
if (chunk) blocks.push(chunk);
|
|
298
532
|
}
|
|
299
|
-
const
|
|
533
|
+
const classesWithApply = classes.filter((c) => emitApplyMixin(c).length > 0);
|
|
534
|
+
const applyAll = classesWithApply.length > 0 ? `
|
|
300
535
|
|
|
301
536
|
export function applyAllGenerated(handlers: {
|
|
302
|
-
${
|
|
537
|
+
${classesWithApply.map((c) => ` ${c.name}: typeof ${c.name};`).join("\n")}
|
|
303
538
|
}): void {
|
|
304
|
-
${
|
|
539
|
+
${classesWithApply.map((c) => ` apply${c.name}Generated(handlers.${c.name});`).join("\n")}
|
|
305
540
|
}
|
|
306
541
|
` : "\nexport {};\n";
|
|
307
542
|
const imports = emitImports(classes, importPath);
|
|
@@ -343,7 +578,7 @@ var CodeGenerator = class {
|
|
|
343
578
|
const { ts, dts } = emitCompanionFile(sourcePath, outputPath, classes, process.cwd());
|
|
344
579
|
const content = ts;
|
|
345
580
|
this.writeOutput(outputPath, content);
|
|
346
|
-
this.writeOutput(outputPath.replace(/\.lombok\.ts$/u, ".lombok.d.ts"), dts);
|
|
581
|
+
this.writeOutput(outputPath.replace(/\.lombok\.ts$/u, ".lombok.augment.d.ts"), dts);
|
|
347
582
|
generated.push({
|
|
348
583
|
sourcePath,
|
|
349
584
|
outputPath,
|
|
@@ -366,7 +601,7 @@ var CodeGenerator = class {
|
|
|
366
601
|
const { ts, dts } = emitCompanionFile(filePath, outputPath, classes, process.cwd());
|
|
367
602
|
const content = ts;
|
|
368
603
|
this.writeOutput(outputPath, content);
|
|
369
|
-
this.writeOutput(outputPath.replace(/\.lombok\.ts$/u, ".lombok.d.ts"), dts);
|
|
604
|
+
this.writeOutput(outputPath.replace(/\.lombok\.ts$/u, ".lombok.augment.d.ts"), dts);
|
|
370
605
|
return {
|
|
371
606
|
sourcePath: filePath,
|
|
372
607
|
outputPath,
|
|
@@ -374,13 +609,57 @@ var CodeGenerator = class {
|
|
|
374
609
|
processedClasses: classes.map((c) => c.name)
|
|
375
610
|
};
|
|
376
611
|
}
|
|
377
|
-
/** Watch
|
|
378
|
-
async watch() {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
);
|
|
612
|
+
/** Watch for source changes and regenerate companion files. */
|
|
613
|
+
async watch(options = {}) {
|
|
614
|
+
const log = options.log ?? ((msg) => console.info(msg));
|
|
615
|
+
const generated = await this.generate();
|
|
616
|
+
log(`Generated ${generated.length} companion file(s). Watching for changes\u2026`);
|
|
617
|
+
if (options.signal?.aborted) return;
|
|
618
|
+
const { watch } = await import('fs');
|
|
619
|
+
const watchers = [];
|
|
620
|
+
const debounceTimers = /* @__PURE__ */ new Map();
|
|
621
|
+
const project = this.createProject();
|
|
622
|
+
const paths = project.getSourceFiles().map((sf) => sf.getFilePath()).filter((p) => this.shouldProcess(p));
|
|
623
|
+
const scheduleRegenerate = (filePath) => {
|
|
624
|
+
const existing = debounceTimers.get(filePath);
|
|
625
|
+
if (existing) clearTimeout(existing);
|
|
626
|
+
debounceTimers.set(
|
|
627
|
+
filePath,
|
|
628
|
+
setTimeout(() => {
|
|
629
|
+
void this.generateForFile(filePath).then((result) => {
|
|
630
|
+
if (result) {
|
|
631
|
+
log(`Regenerated ${relative(process.cwd(), result.outputPath)}`);
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
}, 100)
|
|
635
|
+
);
|
|
636
|
+
};
|
|
637
|
+
for (const filePath of paths) {
|
|
638
|
+
try {
|
|
639
|
+
const watcher = watch(filePath, { persistent: true }, (event) => {
|
|
640
|
+
if (event === "change") scheduleRegenerate(filePath);
|
|
641
|
+
});
|
|
642
|
+
watchers.push(watcher);
|
|
643
|
+
} catch {
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
await new Promise((resolve2) => {
|
|
647
|
+
const onAbort = () => {
|
|
648
|
+
for (const w of watchers) w.close();
|
|
649
|
+
for (const t of debounceTimers.values()) clearTimeout(t);
|
|
650
|
+
resolve2();
|
|
651
|
+
};
|
|
652
|
+
if (options.signal) {
|
|
653
|
+
if (options.signal.aborted) {
|
|
654
|
+
onAbort();
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
});
|
|
382
661
|
}
|
|
383
|
-
// Internal helpers
|
|
662
|
+
// Internal helpers — previously threw for watch stub
|
|
384
663
|
createProject() {
|
|
385
664
|
const tsConfig = resolve(this.options.tsConfigPath);
|
|
386
665
|
if (existsSync(tsConfig)) {
|