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