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/cli/index.cjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
var path = require('path');
|
|
5
|
+
var url = require('url');
|
|
4
6
|
var cac = require('cac');
|
|
5
7
|
var fs = require('fs');
|
|
6
|
-
var path = require('path');
|
|
7
8
|
var tsMorph = require('ts-morph');
|
|
8
9
|
var bundleRequire = require('bundle-require');
|
|
9
10
|
|
|
@@ -78,31 +79,190 @@ function toParameterInfo(param) {
|
|
|
78
79
|
decorators: param.getDecorators().map(toDecoratorInfo)
|
|
79
80
|
};
|
|
80
81
|
}
|
|
82
|
+
|
|
83
|
+
// src/decorators/shared/composition.ts
|
|
84
|
+
var CONFLICTING_CLASS_DECORATOR_PAIRS = [
|
|
85
|
+
["Data", "Value"]
|
|
86
|
+
];
|
|
87
|
+
function classHasDecorator(info, name) {
|
|
88
|
+
return info.decorators.some((d) => d.name === name);
|
|
89
|
+
}
|
|
90
|
+
function validateClassComposition(info) {
|
|
91
|
+
for (const [a, b] of CONFLICTING_CLASS_DECORATOR_PAIRS) {
|
|
92
|
+
if (classHasDecorator(info, a) && classHasDecorator(info, b)) {
|
|
93
|
+
throw new Error(`Class "${info.name}": @${a} and @${b} cannot be used together.`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function validateAllClassCompositions(classes) {
|
|
98
|
+
for (const info of classes) {
|
|
99
|
+
validateClassComposition(info);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
81
102
|
function toImportPath(sourcePath, fromDir) {
|
|
82
103
|
let rel = path.relative(fromDir, sourcePath).replace(/\\/g, "/");
|
|
83
104
|
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
84
105
|
return rel.replace(/\.tsx?$/u, ".js");
|
|
85
106
|
}
|
|
107
|
+
var CODEGEN_CLASS_DECORATORS = [
|
|
108
|
+
"Builder",
|
|
109
|
+
"Data",
|
|
110
|
+
"ToString",
|
|
111
|
+
"Value",
|
|
112
|
+
"Equals",
|
|
113
|
+
"With"
|
|
114
|
+
];
|
|
86
115
|
function hasCodegenClassDecorator(info) {
|
|
87
|
-
return
|
|
116
|
+
return CODEGEN_CLASS_DECORATORS.some((name) => hasClassDecorator(info, name)) || info.fields.some(
|
|
117
|
+
(f) => fieldHasDecorator(f, "Getter") || fieldHasDecorator(f, "Setter") || fieldHasDecorator(f, "With") || fieldHasDecorator(f, "Delegate")
|
|
118
|
+
);
|
|
88
119
|
}
|
|
89
120
|
function hasClassDecorator(info, name) {
|
|
90
121
|
return info.decorators.some((d) => d.name === name);
|
|
91
122
|
}
|
|
123
|
+
function fieldHasDecorator(field, name) {
|
|
124
|
+
return field.decorators.some((d) => d.name === name);
|
|
125
|
+
}
|
|
92
126
|
function fieldExcludesToString(field) {
|
|
93
127
|
return field.decorators.some(
|
|
94
128
|
(d) => d.name === "ToStringExclude" || d.name === "ToString.Exclude"
|
|
95
129
|
);
|
|
96
130
|
}
|
|
131
|
+
function fieldExcludesEquals(field) {
|
|
132
|
+
return field.decorators.some((d) => d.name === "EqualsExclude" || d.name === "Equals.Exclude");
|
|
133
|
+
}
|
|
97
134
|
function visibleFields(info) {
|
|
98
|
-
if (hasClassDecorator(info, "ToString")) {
|
|
135
|
+
if (hasClassDecorator(info, "ToString") || hasClassDecorator(info, "Data") || hasClassDecorator(info, "Value")) {
|
|
99
136
|
return info.fields.filter((f) => !fieldExcludesToString(f));
|
|
100
137
|
}
|
|
101
138
|
return info.fields;
|
|
102
139
|
}
|
|
140
|
+
function equalsFields(info) {
|
|
141
|
+
return info.fields.filter((f) => !fieldExcludesEquals(f));
|
|
142
|
+
}
|
|
143
|
+
function getterName(fieldName) {
|
|
144
|
+
return `get${fieldName.charAt(0).toUpperCase()}${fieldName.slice(1)}`;
|
|
145
|
+
}
|
|
146
|
+
function setterName(fieldName) {
|
|
147
|
+
return `set${fieldName.charAt(0).toUpperCase()}${fieldName.slice(1)}`;
|
|
148
|
+
}
|
|
149
|
+
function withMethodName(fieldName) {
|
|
150
|
+
return `with${fieldName.charAt(0).toUpperCase()}${fieldName.slice(1)}`;
|
|
151
|
+
}
|
|
103
152
|
function builderClassName(className) {
|
|
104
153
|
return `${className}Builder`;
|
|
105
154
|
}
|
|
155
|
+
function hasDataOrValue(info) {
|
|
156
|
+
return hasClassDecorator(info, "Data") || hasClassDecorator(info, "Value");
|
|
157
|
+
}
|
|
158
|
+
function wantsWithMethods(info, field) {
|
|
159
|
+
return hasClassDecorator(info, "Value") || hasClassDecorator(info, "With") || fieldHasDecorator(field, "With");
|
|
160
|
+
}
|
|
161
|
+
function wantsGetter(info, field) {
|
|
162
|
+
return hasDataOrValue(info) || fieldHasDecorator(field, "Getter");
|
|
163
|
+
}
|
|
164
|
+
function wantsSetter(info, field) {
|
|
165
|
+
if (hasClassDecorator(info, "Value")) return false;
|
|
166
|
+
if (hasClassDecorator(info, "Data")) return !effectiveReadonly(info, field);
|
|
167
|
+
return fieldHasDecorator(field, "Setter");
|
|
168
|
+
}
|
|
169
|
+
function wantsEquals(info) {
|
|
170
|
+
return hasClassDecorator(info, "Data") || hasClassDecorator(info, "Value") || hasClassDecorator(info, "Equals");
|
|
171
|
+
}
|
|
172
|
+
function wantsToString(info) {
|
|
173
|
+
return hasClassDecorator(info, "ToString") || hasClassDecorator(info, "Data") || hasClassDecorator(info, "Value");
|
|
174
|
+
}
|
|
175
|
+
function effectiveReadonly(info, field) {
|
|
176
|
+
if (field.isReadonly) return true;
|
|
177
|
+
const defaults = getFieldDefaultsOptions(info);
|
|
178
|
+
return defaults?.makeFinal === true;
|
|
179
|
+
}
|
|
180
|
+
function getFieldDefaultsOptions(info) {
|
|
181
|
+
const dec = info.decorators.find((d) => d.name === "FieldDefaults");
|
|
182
|
+
if (!dec) return void 0;
|
|
183
|
+
const [first] = dec.arguments;
|
|
184
|
+
if (typeof first === "string" && first.startsWith("{")) {
|
|
185
|
+
try {
|
|
186
|
+
const parsed = JSON.parse(first.replace(/(\w+):/g, '"$1":').replace(/'/g, '"'));
|
|
187
|
+
return {
|
|
188
|
+
level: parsed.level ?? "public",
|
|
189
|
+
makeFinal: parsed.makeFinal ?? false
|
|
190
|
+
};
|
|
191
|
+
} catch {
|
|
192
|
+
return { level: "public", makeFinal: false };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return { level: "public", makeFinal: false };
|
|
196
|
+
}
|
|
197
|
+
function hasFluentAccessors(info) {
|
|
198
|
+
const dec = info.decorators.find((d) => d.name === "Accessors");
|
|
199
|
+
if (!dec) return false;
|
|
200
|
+
const [first] = dec.arguments;
|
|
201
|
+
if (typeof first === "string") {
|
|
202
|
+
return first.includes("chain") || first.includes("fluent");
|
|
203
|
+
}
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
function getDelegateMethods(field) {
|
|
207
|
+
const dec = field.decorators.find((d) => d.name === "Delegate");
|
|
208
|
+
if (!dec || dec.arguments.length === 0) return [];
|
|
209
|
+
if (dec.arguments.length === 1 && String(dec.arguments[0]).startsWith("[")) {
|
|
210
|
+
try {
|
|
211
|
+
return JSON.parse(String(dec.arguments[0]).replace(/'/g, '"'));
|
|
212
|
+
} catch {
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return dec.arguments.map((a) => String(a).replace(/^['"]|['"]$/g, ""));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/codegen/emitters/accessors-emit.ts
|
|
220
|
+
function emitGetterFn(info, field) {
|
|
221
|
+
const g = getterName(field.name);
|
|
222
|
+
return `
|
|
223
|
+
function ${info.name}_${g}(this: ${info.name}): ${field.type} {
|
|
224
|
+
return this.${field.name};
|
|
225
|
+
}`.trim();
|
|
226
|
+
}
|
|
227
|
+
function emitSetterFn(info, field) {
|
|
228
|
+
const s = setterName(field.name);
|
|
229
|
+
const fluent = hasFluentAccessors(info);
|
|
230
|
+
const ret = fluent ? `return this;` : "";
|
|
231
|
+
const returnType = fluent ? info.name : "void";
|
|
232
|
+
return `
|
|
233
|
+
function ${info.name}_${s}(this: ${info.name}, value: ${field.type}): ${returnType} {
|
|
234
|
+
this.${field.name} = value;
|
|
235
|
+
${ret}
|
|
236
|
+
}`.trim();
|
|
237
|
+
}
|
|
238
|
+
function emitAccessorFns(info) {
|
|
239
|
+
const fns = [];
|
|
240
|
+
for (const field of info.fields) {
|
|
241
|
+
if (wantsGetter(info, field)) {
|
|
242
|
+
fns.push(emitGetterFn(info, field));
|
|
243
|
+
}
|
|
244
|
+
if (wantsSetter(info, field)) {
|
|
245
|
+
fns.push(emitSetterFn(info, field));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return fns.join("\n\n");
|
|
249
|
+
}
|
|
250
|
+
function emitAccessorApplyAssignments(info) {
|
|
251
|
+
const assignments = [];
|
|
252
|
+
for (const field of info.fields) {
|
|
253
|
+
if (wantsGetter(info, field)) {
|
|
254
|
+
assignments.push(
|
|
255
|
+
`prototype.${getterName(field.name)} = ${info.name}_${getterName(field.name)};`
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
if (wantsSetter(info, field)) {
|
|
259
|
+
assignments.push(
|
|
260
|
+
`prototype.${setterName(field.name)} = ${info.name}_${setterName(field.name)};`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return assignments;
|
|
265
|
+
}
|
|
106
266
|
|
|
107
267
|
// src/codegen/emitters/builder.ts
|
|
108
268
|
function emitBuilderClass(info) {
|
|
@@ -140,16 +300,8 @@ ${info.fields.map((f) => ` instance.${f.name} = this._${f.name}${f.isOptional
|
|
|
140
300
|
}
|
|
141
301
|
}`.trim();
|
|
142
302
|
}
|
|
143
|
-
function
|
|
144
|
-
const
|
|
145
|
-
const lines = [
|
|
146
|
-
"// Auto-generated type augmentation by lombok-typescript.",
|
|
147
|
-
"// Do not edit. Regenerate via `lombok-ts generate`.",
|
|
148
|
-
"",
|
|
149
|
-
"export {};",
|
|
150
|
-
"",
|
|
151
|
-
`declare module '${relSource}' {`
|
|
152
|
-
];
|
|
303
|
+
function emitDeclarationModuleBlock(relSource, classes) {
|
|
304
|
+
const lines = [`declare module '${relSource}' {`];
|
|
153
305
|
for (const info of classes) {
|
|
154
306
|
if (hasClassDecorator(info, "Builder")) {
|
|
155
307
|
const builderName = builderClassName(info.name);
|
|
@@ -164,24 +316,33 @@ function emitDeclarationShim(sourcePath, companionOutputPath, classes) {
|
|
|
164
316
|
}
|
|
165
317
|
if (hasClassDecorator(info, "Builder")) {
|
|
166
318
|
const builderName = builderClassName(info.name);
|
|
167
|
-
lines.push(`
|
|
168
|
-
lines.push(`
|
|
319
|
+
lines.push(` namespace ${info.name} {`);
|
|
320
|
+
lines.push(` export function builder(): ${builderName};`);
|
|
169
321
|
lines.push(" }");
|
|
170
322
|
lines.push("");
|
|
171
323
|
}
|
|
172
324
|
const augments = [];
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
325
|
+
for (const f of info.fields) {
|
|
326
|
+
if (wantsGetter(info, f)) {
|
|
327
|
+
augments.push(` ${getterName(f.name)}(): ${f.type};`);
|
|
328
|
+
}
|
|
329
|
+
if (wantsSetter(info, f)) {
|
|
330
|
+
const ret = hasClassDecorator(info, "Accessors") ? info.name : "void";
|
|
331
|
+
augments.push(` ${setterName(f.name)}(value: ${f.type}): ${ret};`);
|
|
332
|
+
}
|
|
333
|
+
if (wantsWithMethods(info, f)) {
|
|
334
|
+
augments.push(` ${withMethodName(f.name)}(value: ${f.type}): ${info.name};`);
|
|
335
|
+
}
|
|
336
|
+
if (fieldHasDecorator(f, "Delegate")) {
|
|
337
|
+
for (const method of getDelegateMethods(f)) {
|
|
338
|
+
augments.push(` ${method}(...args: unknown[]): unknown;`);
|
|
180
339
|
}
|
|
181
340
|
}
|
|
341
|
+
}
|
|
342
|
+
if (wantsEquals(info)) {
|
|
182
343
|
augments.push(` equals(other: ${info.name} | null | undefined): boolean;`);
|
|
183
|
-
|
|
184
|
-
|
|
344
|
+
}
|
|
345
|
+
if (wantsToString(info)) {
|
|
185
346
|
augments.push(" toString(): string;");
|
|
186
347
|
}
|
|
187
348
|
if (augments.length > 0) {
|
|
@@ -189,11 +350,99 @@ function emitDeclarationShim(sourcePath, companionOutputPath, classes) {
|
|
|
189
350
|
lines.push(...augments);
|
|
190
351
|
lines.push(" }");
|
|
191
352
|
}
|
|
353
|
+
if (hasClassDecorator(info, "Equals")) {
|
|
354
|
+
lines.push(` namespace ${info.name} {`);
|
|
355
|
+
lines.push(
|
|
356
|
+
` export function equals(a: ${info.name} | null | undefined, b: ${info.name} | null | undefined): boolean;`
|
|
357
|
+
);
|
|
358
|
+
lines.push(" }");
|
|
359
|
+
lines.push("");
|
|
360
|
+
}
|
|
192
361
|
}
|
|
193
362
|
lines.push("}");
|
|
194
|
-
lines.push("");
|
|
195
363
|
return lines.join("\n");
|
|
196
364
|
}
|
|
365
|
+
function emitDeclarationShim(sourcePath, companionOutputPath, classes) {
|
|
366
|
+
const relSource = toImportPath(sourcePath, path.dirname(companionOutputPath));
|
|
367
|
+
const moduleBlock = emitDeclarationModuleBlock(relSource, classes);
|
|
368
|
+
return [
|
|
369
|
+
"// Auto-generated type augmentation by lombok-typescript.",
|
|
370
|
+
"// Do not edit. Regenerate via `lombok-ts generate`.",
|
|
371
|
+
"",
|
|
372
|
+
"export {};",
|
|
373
|
+
"",
|
|
374
|
+
moduleBlock,
|
|
375
|
+
""
|
|
376
|
+
].join("\n");
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// src/codegen/emitters/delegate-emit.ts
|
|
380
|
+
function emitDelegateFns(info) {
|
|
381
|
+
const fns = [];
|
|
382
|
+
for (const field of info.fields) {
|
|
383
|
+
if (!fieldHasDecorator(field, "Delegate")) continue;
|
|
384
|
+
const methods = getDelegateMethods(field);
|
|
385
|
+
for (const method of methods) {
|
|
386
|
+
fns.push(
|
|
387
|
+
`
|
|
388
|
+
function ${info.name}_${method}(this: ${info.name}, ...args: unknown[]): unknown {
|
|
389
|
+
const target = this.${field.name} as { ${method}: (...a: unknown[]) => unknown };
|
|
390
|
+
return target.${method}(...args);
|
|
391
|
+
}`.trim()
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return fns.join("\n\n");
|
|
396
|
+
}
|
|
397
|
+
function emitDelegateApplyAssignments(info) {
|
|
398
|
+
const assignments = [];
|
|
399
|
+
for (const field of info.fields) {
|
|
400
|
+
if (!fieldHasDecorator(field, "Delegate")) continue;
|
|
401
|
+
for (const method of getDelegateMethods(field)) {
|
|
402
|
+
assignments.push(`prototype.${method} = ${info.name}_${method};`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return assignments;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// src/codegen/emitters/equals-emit.ts
|
|
409
|
+
function emitEqualsFn(info) {
|
|
410
|
+
if (!wantsEquals(info)) return "";
|
|
411
|
+
const fields = equalsFields(info);
|
|
412
|
+
const checks = fields.map((f) => `this.${f.name} === other.${f.name}`).join(" &&\n ");
|
|
413
|
+
return `
|
|
414
|
+
function ${info.name}_equals(this: ${info.name}, other: ${info.name} | null | undefined): boolean {
|
|
415
|
+
if (other === null || other === undefined) return false;
|
|
416
|
+
if (!(other instanceof (this.constructor as typeof ${info.name}))) return false;
|
|
417
|
+
return ${checks || "true"};
|
|
418
|
+
}`.trim();
|
|
419
|
+
}
|
|
420
|
+
function emitEqualsStaticFn(info) {
|
|
421
|
+
if (!hasClassDecorator(info, "Equals")) return "";
|
|
422
|
+
return `
|
|
423
|
+
function ${info.name}_equalsStatic(a: ${info.name} | null | undefined, b: ${info.name} | null | undefined): boolean {
|
|
424
|
+
if (a === b) return true;
|
|
425
|
+
if (a === null || a === undefined || b === null || b === undefined) return false;
|
|
426
|
+
return a.equals(b);
|
|
427
|
+
}`.trim();
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// src/codegen/emitters/with-emit.ts
|
|
431
|
+
function emitSingleWithFn(info, field) {
|
|
432
|
+
const method = withMethodName(field.name);
|
|
433
|
+
return `
|
|
434
|
+
function ${info.name}_${method}(this: ${info.name}, value: ${field.type}): ${info.name} {
|
|
435
|
+
const copy = Object.create(Object.getPrototypeOf(this)) as ${info.name};
|
|
436
|
+
Object.assign(copy, this);
|
|
437
|
+
copy.${field.name} = value;
|
|
438
|
+
return copy;
|
|
439
|
+
}`.trim();
|
|
440
|
+
}
|
|
441
|
+
function emitWithFns(info) {
|
|
442
|
+
const fields = info.fields.filter((f) => wantsWithMethods(info, f));
|
|
443
|
+
if (fields.length === 0) return "";
|
|
444
|
+
return fields.map((f) => emitSingleWithFn(info, f)).join("\n\n");
|
|
445
|
+
}
|
|
197
446
|
|
|
198
447
|
// src/codegen/emitters/index.ts
|
|
199
448
|
function emitImports(classes, importPath) {
|
|
@@ -204,7 +453,7 @@ function emitImports(classes, importPath) {
|
|
|
204
453
|
`;
|
|
205
454
|
}
|
|
206
455
|
function emitToStringFn(info) {
|
|
207
|
-
if (!
|
|
456
|
+
if (!wantsToString(info)) return "";
|
|
208
457
|
const fields = visibleFields(info);
|
|
209
458
|
const parts = fields.map((f) => `${f.name}=\${String(this.${f.name})}`).join(", ");
|
|
210
459
|
return `
|
|
@@ -214,19 +463,20 @@ function ${info.name}_toString(this: ${info.name}): string {
|
|
|
214
463
|
}
|
|
215
464
|
function emitApplyMixin(info) {
|
|
216
465
|
const assignments = [];
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
assignments.push(`prototype.${g} = ${info.name}_${g};`);
|
|
221
|
-
if (!f.isReadonly) {
|
|
222
|
-
const s = `set${f.name.charAt(0).toUpperCase()}${f.name.slice(1)}`;
|
|
223
|
-
assignments.push(`prototype.${s} = ${info.name}_${s};`);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
466
|
+
assignments.push(...emitAccessorApplyAssignments(info));
|
|
467
|
+
assignments.push(...emitDelegateApplyAssignments(info));
|
|
468
|
+
if (wantsEquals(info)) {
|
|
226
469
|
assignments.push(`prototype.equals = ${info.name}_equals;`);
|
|
470
|
+
}
|
|
471
|
+
if (wantsToString(info)) {
|
|
227
472
|
assignments.push(`prototype.toString = ${info.name}_toString;`);
|
|
228
|
-
}
|
|
229
|
-
|
|
473
|
+
}
|
|
474
|
+
for (const field of info.fields) {
|
|
475
|
+
if (wantsWithMethods(info, field)) {
|
|
476
|
+
assignments.push(
|
|
477
|
+
`prototype.${withMethodName(field.name)} = ${info.name}_${withMethodName(field.name)};`
|
|
478
|
+
);
|
|
479
|
+
}
|
|
230
480
|
}
|
|
231
481
|
if (hasClassDecorator(info, "Builder")) {
|
|
232
482
|
const bName = builderClassName(info.name);
|
|
@@ -234,6 +484,11 @@ function emitApplyMixin(info) {
|
|
|
234
484
|
`(ctor as typeof ${info.name} & { builder(): ${bName} }).builder = ${info.name}_builder;`
|
|
235
485
|
);
|
|
236
486
|
}
|
|
487
|
+
if (hasClassDecorator(info, "Equals")) {
|
|
488
|
+
assignments.push(
|
|
489
|
+
`(ctor as typeof ${info.name} & { equals(a: ${info.name} | null | undefined, b: ${info.name} | null | undefined): boolean }).equals = ${info.name}_equalsStatic;`
|
|
490
|
+
);
|
|
491
|
+
}
|
|
237
492
|
if (assignments.length === 0) return "";
|
|
238
493
|
return `
|
|
239
494
|
export function apply${info.name}Generated(ctor: typeof ${info.name}): void {
|
|
@@ -241,39 +496,6 @@ export function apply${info.name}Generated(ctor: typeof ${info.name}): void {
|
|
|
241
496
|
${assignments.join("\n ")}
|
|
242
497
|
}`.trim();
|
|
243
498
|
}
|
|
244
|
-
function emitDataFns(info) {
|
|
245
|
-
if (!hasClassDecorator(info, "Data")) return "";
|
|
246
|
-
const fns = [];
|
|
247
|
-
for (const f of info.fields) {
|
|
248
|
-
const g = `get${f.name.charAt(0).toUpperCase()}${f.name.slice(1)}`;
|
|
249
|
-
fns.push(
|
|
250
|
-
`
|
|
251
|
-
function ${info.name}_${g}(this: ${info.name}): ${f.type} {
|
|
252
|
-
return this.${f.name};
|
|
253
|
-
}`.trim()
|
|
254
|
-
);
|
|
255
|
-
if (!f.isReadonly) {
|
|
256
|
-
const s = `set${f.name.charAt(0).toUpperCase()}${f.name.slice(1)}`;
|
|
257
|
-
fns.push(
|
|
258
|
-
`
|
|
259
|
-
function ${info.name}_${s}(this: ${info.name}, value: ${f.type}): void {
|
|
260
|
-
this.${f.name} = value;
|
|
261
|
-
}`.trim()
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
const equalsBody = info.fields.map((f) => `this.${f.name} === other.${f.name}`).join(" &&\n ");
|
|
266
|
-
fns.push(
|
|
267
|
-
`
|
|
268
|
-
function ${info.name}_equals(this: ${info.name}, other: ${info.name} | null | undefined): boolean {
|
|
269
|
-
if (other === null || other === undefined) return false;
|
|
270
|
-
if (!(other instanceof (this.constructor as typeof ${info.name}))) return false;
|
|
271
|
-
return ${equalsBody || "true"};
|
|
272
|
-
}`.trim()
|
|
273
|
-
);
|
|
274
|
-
fns.push(emitToStringFn(info));
|
|
275
|
-
return fns.filter(Boolean).join("\n\n");
|
|
276
|
-
}
|
|
277
499
|
function emitBuilderFn(info) {
|
|
278
500
|
if (!hasClassDecorator(info, "Builder")) return "";
|
|
279
501
|
const bName = builderClassName(info.name);
|
|
@@ -282,7 +504,30 @@ function ${info.name}_builder(): ${bName} {
|
|
|
282
504
|
return ${bName}.builder();
|
|
283
505
|
}`.trim();
|
|
284
506
|
}
|
|
507
|
+
function emitClassCompanionBlocks(info) {
|
|
508
|
+
const blocks = [];
|
|
509
|
+
const builder = emitBuilderClass(info);
|
|
510
|
+
if (builder) blocks.push(builder);
|
|
511
|
+
const accessors = emitAccessorFns(info);
|
|
512
|
+
if (accessors) blocks.push(accessors);
|
|
513
|
+
const withFns = emitWithFns(info);
|
|
514
|
+
if (withFns) blocks.push(withFns);
|
|
515
|
+
const equalsFn = emitEqualsFn(info);
|
|
516
|
+
if (equalsFn) blocks.push(equalsFn);
|
|
517
|
+
const equalsStatic = emitEqualsStaticFn(info);
|
|
518
|
+
if (equalsStatic) blocks.push(equalsStatic);
|
|
519
|
+
const toString = emitToStringFn(info);
|
|
520
|
+
if (toString) blocks.push(toString);
|
|
521
|
+
const delegate = emitDelegateFns(info);
|
|
522
|
+
if (delegate) blocks.push(delegate);
|
|
523
|
+
const builderFn = emitBuilderFn(info);
|
|
524
|
+
if (builderFn) blocks.push(builderFn);
|
|
525
|
+
const apply = emitApplyMixin(info);
|
|
526
|
+
if (apply) blocks.push(apply);
|
|
527
|
+
return blocks.filter(Boolean).join("\n\n");
|
|
528
|
+
}
|
|
285
529
|
function emitCompanionFile(sourcePath, companionOutputPath, classes, cwd) {
|
|
530
|
+
validateAllClassCompositions(classes);
|
|
286
531
|
const header = [
|
|
287
532
|
"// Auto-generated by lombok-typescript.",
|
|
288
533
|
"// Source: " + path.relative(cwd, sourcePath).replace(/\\/g, "/"),
|
|
@@ -292,25 +537,16 @@ function emitCompanionFile(sourcePath, companionOutputPath, classes, cwd) {
|
|
|
292
537
|
const importPath = toImportPath(sourcePath, path.dirname(companionOutputPath));
|
|
293
538
|
const blocks = [];
|
|
294
539
|
for (const info of classes) {
|
|
295
|
-
const
|
|
296
|
-
if (
|
|
297
|
-
if (hasClassDecorator(info, "Data")) {
|
|
298
|
-
blocks.push(emitDataFns(info));
|
|
299
|
-
} else {
|
|
300
|
-
const ts2 = emitToStringFn(info);
|
|
301
|
-
if (ts2) blocks.push(ts2);
|
|
302
|
-
}
|
|
303
|
-
const builderFn = emitBuilderFn(info);
|
|
304
|
-
if (builderFn) blocks.push(builderFn);
|
|
305
|
-
const apply = emitApplyMixin(info);
|
|
306
|
-
if (apply) blocks.push(apply);
|
|
540
|
+
const chunk = emitClassCompanionBlocks(info);
|
|
541
|
+
if (chunk) blocks.push(chunk);
|
|
307
542
|
}
|
|
308
|
-
const
|
|
543
|
+
const classesWithApply = classes.filter((c) => emitApplyMixin(c).length > 0);
|
|
544
|
+
const applyAll = classesWithApply.length > 0 ? `
|
|
309
545
|
|
|
310
546
|
export function applyAllGenerated(handlers: {
|
|
311
|
-
${
|
|
547
|
+
${classesWithApply.map((c) => ` ${c.name}: typeof ${c.name};`).join("\n")}
|
|
312
548
|
}): void {
|
|
313
|
-
${
|
|
549
|
+
${classesWithApply.map((c) => ` apply${c.name}Generated(handlers.${c.name});`).join("\n")}
|
|
314
550
|
}
|
|
315
551
|
` : "\nexport {};\n";
|
|
316
552
|
const imports = emitImports(classes, importPath);
|
|
@@ -352,7 +588,7 @@ var CodeGenerator = class {
|
|
|
352
588
|
const { ts, dts } = emitCompanionFile(sourcePath, outputPath, classes, process.cwd());
|
|
353
589
|
const content = ts;
|
|
354
590
|
this.writeOutput(outputPath, content);
|
|
355
|
-
this.writeOutput(outputPath.replace(/\.lombok\.ts$/u, ".lombok.d.ts"), dts);
|
|
591
|
+
this.writeOutput(outputPath.replace(/\.lombok\.ts$/u, ".lombok.augment.d.ts"), dts);
|
|
356
592
|
generated.push({
|
|
357
593
|
sourcePath,
|
|
358
594
|
outputPath,
|
|
@@ -375,7 +611,7 @@ var CodeGenerator = class {
|
|
|
375
611
|
const { ts, dts } = emitCompanionFile(filePath, outputPath, classes, process.cwd());
|
|
376
612
|
const content = ts;
|
|
377
613
|
this.writeOutput(outputPath, content);
|
|
378
|
-
this.writeOutput(outputPath.replace(/\.lombok\.ts$/u, ".lombok.d.ts"), dts);
|
|
614
|
+
this.writeOutput(outputPath.replace(/\.lombok\.ts$/u, ".lombok.augment.d.ts"), dts);
|
|
379
615
|
return {
|
|
380
616
|
sourcePath: filePath,
|
|
381
617
|
outputPath,
|
|
@@ -383,13 +619,57 @@ var CodeGenerator = class {
|
|
|
383
619
|
processedClasses: classes.map((c) => c.name)
|
|
384
620
|
};
|
|
385
621
|
}
|
|
386
|
-
/** Watch
|
|
387
|
-
async watch() {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
);
|
|
622
|
+
/** Watch for source changes and regenerate companion files. */
|
|
623
|
+
async watch(options = {}) {
|
|
624
|
+
const log = options.log ?? ((msg) => console.info(msg));
|
|
625
|
+
const generated = await this.generate();
|
|
626
|
+
log(`Generated ${generated.length} companion file(s). Watching for changes\u2026`);
|
|
627
|
+
if (options.signal?.aborted) return;
|
|
628
|
+
const { watch } = await import('fs');
|
|
629
|
+
const watchers = [];
|
|
630
|
+
const debounceTimers = /* @__PURE__ */ new Map();
|
|
631
|
+
const project = this.createProject();
|
|
632
|
+
const paths = project.getSourceFiles().map((sf) => sf.getFilePath()).filter((p) => this.shouldProcess(p));
|
|
633
|
+
const scheduleRegenerate = (filePath) => {
|
|
634
|
+
const existing = debounceTimers.get(filePath);
|
|
635
|
+
if (existing) clearTimeout(existing);
|
|
636
|
+
debounceTimers.set(
|
|
637
|
+
filePath,
|
|
638
|
+
setTimeout(() => {
|
|
639
|
+
void this.generateForFile(filePath).then((result) => {
|
|
640
|
+
if (result) {
|
|
641
|
+
log(`Regenerated ${path.relative(process.cwd(), result.outputPath)}`);
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
}, 100)
|
|
645
|
+
);
|
|
646
|
+
};
|
|
647
|
+
for (const filePath of paths) {
|
|
648
|
+
try {
|
|
649
|
+
const watcher = watch(filePath, { persistent: true }, (event) => {
|
|
650
|
+
if (event === "change") scheduleRegenerate(filePath);
|
|
651
|
+
});
|
|
652
|
+
watchers.push(watcher);
|
|
653
|
+
} catch {
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
await new Promise((resolve6) => {
|
|
657
|
+
const onAbort = () => {
|
|
658
|
+
for (const w of watchers) w.close();
|
|
659
|
+
for (const t of debounceTimers.values()) clearTimeout(t);
|
|
660
|
+
resolve6();
|
|
661
|
+
};
|
|
662
|
+
if (options.signal) {
|
|
663
|
+
if (options.signal.aborted) {
|
|
664
|
+
onAbort();
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
});
|
|
391
671
|
}
|
|
392
|
-
// Internal helpers
|
|
672
|
+
// Internal helpers — previously threw for watch stub
|
|
393
673
|
createProject() {
|
|
394
674
|
const tsConfig = path.resolve(this.options.tsConfigPath);
|
|
395
675
|
if (fs.existsSync(tsConfig)) {
|
|
@@ -534,13 +814,24 @@ async function runInit(opts = {}) {
|
|
|
534
814
|
// src/cli/commands/watch.ts
|
|
535
815
|
async function runWatch(opts = {}) {
|
|
536
816
|
const log = opts.log ?? ((msg) => console.info(msg));
|
|
537
|
-
|
|
538
|
-
|
|
817
|
+
const loaded = await loadConfig(opts.cwd);
|
|
818
|
+
const codegenOpts = {
|
|
819
|
+
...loaded?.config.codegen ?? {},
|
|
820
|
+
...opts.overrides,
|
|
821
|
+
watch: true
|
|
822
|
+
};
|
|
823
|
+
if (loaded) {
|
|
824
|
+
log(`Loaded config from ${loaded.filepath}`);
|
|
825
|
+
} else {
|
|
826
|
+
log("No lombok.config.* file found, using defaults.");
|
|
827
|
+
}
|
|
828
|
+
const gen = new CodeGenerator(codegenOpts);
|
|
829
|
+
await gen.watch({ log, signal: opts.signal });
|
|
539
830
|
}
|
|
540
831
|
|
|
541
832
|
// src/cli/index.ts
|
|
542
833
|
var CLI_NAME = "lombok-ts";
|
|
543
|
-
var CLI_VERSION = "0.
|
|
834
|
+
var CLI_VERSION = "0.4.0-pre";
|
|
544
835
|
function buildCli() {
|
|
545
836
|
const cli = cac.cac(CLI_NAME);
|
|
546
837
|
cli.command("generate", "Run codegen once against the configured source files").alias("gen").option("--output-dir <dir>", "Override codegen output directory").option("--ts-config <path>", "Path to tsconfig.json (default: tsconfig.json)").action(async (options) => {
|
|
@@ -551,7 +842,7 @@ function buildCli() {
|
|
|
551
842
|
}
|
|
552
843
|
});
|
|
553
844
|
});
|
|
554
|
-
cli.command("watch", "Watch source files and regenerate on change
|
|
845
|
+
cli.command("watch", "Watch source files and regenerate on change").action(async () => {
|
|
555
846
|
await runWatch();
|
|
556
847
|
});
|
|
557
848
|
cli.command("init", "Create a starter lombok.config.ts in the current directory").option("--force", "Overwrite an existing config file").action(async (options) => {
|
|
@@ -569,7 +860,7 @@ async function runCli(argv = process.argv) {
|
|
|
569
860
|
cli.parse(argv, { run: false });
|
|
570
861
|
await cli.runMatchedCommand();
|
|
571
862
|
}
|
|
572
|
-
var isMain = typeof process !== "undefined" && typeof (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) === "string" && process.argv[1] !== void 0 && (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)).
|
|
863
|
+
var isMain = typeof process !== "undefined" && typeof (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) === "string" && process.argv[1] !== void 0 && url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))) === path.resolve(process.argv[1]);
|
|
573
864
|
if (isMain) {
|
|
574
865
|
runCli().catch((err) => {
|
|
575
866
|
console.error(err);
|