lombok-typescript 0.6.0 → 0.8.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 +24 -6
- package/dist/cli/index.cjs +157 -5
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +157 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/codegen/index.cjs +157 -5
- package/dist/codegen/index.cjs.map +1 -1
- package/dist/codegen/index.js +157 -5
- package/dist/codegen/index.js.map +1 -1
- package/dist/legacy/index.cjs +241 -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 +232 -1
- package/dist/legacy/index.js.map +1 -1
- package/dist/stage3/index.cjs +257 -1
- 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 +248 -2
- package/dist/stage3/index.js.map +1 -1
- package/dist/{chain-of-responsibility-CQ7votcC.d.cts → visitor-B5ByGIWE.d.cts} +26 -1
- package/dist/{chain-of-responsibility-Dwsaoe4x.d.ts → visitor-BqARKwCX.d.ts} +26 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -2,18 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
TypeScript decorators inspired by Project Lombok and common design patterns. Supports legacy `experimentalDecorators` and Stage 3 decorators via separate entry points.
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/lombok-typescript/v/0.8.0)
|
|
6
|
+
[](https://www.npmjs.com/package/lombok-typescript/v/preview)
|
|
7
|
+
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
9
|
+
[](https://nodejs.org/)
|
|
10
|
+
[](https://opensource.org/licenses/MIT)
|
|
11
|
+
|
|
12
|
+
[](https://bundlephobia.com/package/lombok-typescript@0.8.0)
|
|
13
|
+
[](https://deps.dev/npm/lombok-typescript/0.8.0)
|
|
14
|
+
[](https://socket.dev/npm/package/lombok-typescript)
|
|
15
|
+
[](https://snyk.io/advisor/npm-package/lombok-typescript)
|
|
10
16
|
|
|
11
17
|
## Install
|
|
12
18
|
|
|
13
19
|
Install this release:
|
|
14
20
|
|
|
15
21
|
```bash
|
|
16
|
-
npm install lombok-typescript@0.
|
|
22
|
+
npm install lombok-typescript@0.8.0
|
|
17
23
|
```
|
|
18
24
|
|
|
19
25
|
Install the current `latest` tag (stable release):
|
|
@@ -52,6 +58,7 @@ npm install lombok-typescript@preview
|
|
|
52
58
|
- @Singleton
|
|
53
59
|
- @Prototype
|
|
54
60
|
- @Factory
|
|
61
|
+
- @AbstractFactory
|
|
55
62
|
|
|
56
63
|
### Behavioral patterns
|
|
57
64
|
|
|
@@ -62,6 +69,17 @@ npm install lombok-typescript@preview
|
|
|
62
69
|
- @Observable
|
|
63
70
|
- @ChainOfResponsibility
|
|
64
71
|
- @Iterable
|
|
72
|
+
- @Visitor
|
|
73
|
+
- @Visitable
|
|
74
|
+
- @Hook
|
|
75
|
+
|
|
76
|
+
### Structural patterns
|
|
77
|
+
|
|
78
|
+
- @Flyweight
|
|
79
|
+
- @Proxy
|
|
80
|
+
- @Composite
|
|
81
|
+
- @Wraps
|
|
82
|
+
- @TemplateMethod
|
|
65
83
|
|
|
66
84
|
### TypeScript utilities
|
|
67
85
|
|
package/dist/cli/index.cjs
CHANGED
|
@@ -110,7 +110,10 @@ var CODEGEN_CLASS_DECORATORS = [
|
|
|
110
110
|
"ToString",
|
|
111
111
|
"Value",
|
|
112
112
|
"Equals",
|
|
113
|
-
"With"
|
|
113
|
+
"With",
|
|
114
|
+
"TemplateMethod",
|
|
115
|
+
"AbstractFactory",
|
|
116
|
+
"Visitable"
|
|
114
117
|
];
|
|
115
118
|
function hasCodegenClassDecorator(info) {
|
|
116
119
|
return CODEGEN_CLASS_DECORATORS.some((name) => hasClassDecorator(info, name)) || info.fields.some(
|
|
@@ -215,6 +218,54 @@ function getDelegateMethods(field) {
|
|
|
215
218
|
}
|
|
216
219
|
return dec.arguments.map((a) => String(a).replace(/^['"]|['"]$/g, ""));
|
|
217
220
|
}
|
|
221
|
+
function parseDecoratorObjectArg(dec) {
|
|
222
|
+
if (!dec || dec.arguments.length === 0) return {};
|
|
223
|
+
const [first] = dec.arguments;
|
|
224
|
+
if (typeof first !== "string" || !first.startsWith("{")) return {};
|
|
225
|
+
try {
|
|
226
|
+
return JSON.parse(first.replace(/(\w+):/g, '"$1":').replace(/'/g, '"'));
|
|
227
|
+
} catch {
|
|
228
|
+
return {};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function parseDecoratorArrayArg(dec) {
|
|
232
|
+
if (!dec || dec.arguments.length === 0) return [];
|
|
233
|
+
const [first] = dec.arguments;
|
|
234
|
+
const text = String(first);
|
|
235
|
+
if (!text.startsWith("[")) return [];
|
|
236
|
+
try {
|
|
237
|
+
return JSON.parse(text.replace(/'/g, '"'));
|
|
238
|
+
} catch {
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function getAbstractFactoryProducts(info) {
|
|
243
|
+
const dec = info.decorators.find((d) => d.name === "AbstractFactory");
|
|
244
|
+
const fromArray = parseDecoratorArrayArg(dec);
|
|
245
|
+
if (fromArray.length > 0) return fromArray;
|
|
246
|
+
const obj = parseDecoratorObjectArg(dec);
|
|
247
|
+
return Array.isArray(obj.products) ? obj.products : [];
|
|
248
|
+
}
|
|
249
|
+
function getTemplateMethodSteps(info) {
|
|
250
|
+
const dec = info.decorators.find((d) => d.name === "TemplateMethod");
|
|
251
|
+
const obj = parseDecoratorObjectArg(dec);
|
|
252
|
+
return Array.isArray(obj.steps) ? obj.steps : [];
|
|
253
|
+
}
|
|
254
|
+
function getTemplateMethodName(info) {
|
|
255
|
+
const dec = info.decorators.find((d) => d.name === "TemplateMethod");
|
|
256
|
+
const obj = parseDecoratorObjectArg(dec);
|
|
257
|
+
return typeof obj.template === "string" ? obj.template : "execute";
|
|
258
|
+
}
|
|
259
|
+
function getHookMethodNames(info) {
|
|
260
|
+
return info.methods.filter((m) => m.decorators.some((d) => d.name === "Hook")).map((m) => {
|
|
261
|
+
const hookDec = m.decorators.find((d) => d.name === "Hook");
|
|
262
|
+
const opts = parseDecoratorObjectArg(hookDec);
|
|
263
|
+
return typeof opts.name === "string" ? opts.name : m.name;
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
function visitMethodName(className) {
|
|
267
|
+
return `visit${className}`;
|
|
268
|
+
}
|
|
218
269
|
|
|
219
270
|
// src/codegen/emitters/accessors-emit.ts
|
|
220
271
|
function emitGetterFn(info, field) {
|
|
@@ -264,6 +315,23 @@ function emitAccessorApplyAssignments(info) {
|
|
|
264
315
|
return assignments;
|
|
265
316
|
}
|
|
266
317
|
|
|
318
|
+
// src/codegen/emitters/abstract-factory-emit.ts
|
|
319
|
+
function emitAbstractFactoryMixin(info) {
|
|
320
|
+
if (!hasClassDecorator(info, "AbstractFactory")) return "";
|
|
321
|
+
const products = getAbstractFactoryProducts(info);
|
|
322
|
+
if (products.length === 0) {
|
|
323
|
+
throw new Error(`@AbstractFactory on ${info.name}: product list is empty`);
|
|
324
|
+
}
|
|
325
|
+
const methods = products.map((product) => {
|
|
326
|
+
const methodName = `create${product}`;
|
|
327
|
+
return ` abstract ${methodName}(): ${product};`;
|
|
328
|
+
}).join("\n");
|
|
329
|
+
return `
|
|
330
|
+
export abstract class ${info.name}Mixin {
|
|
331
|
+
${methods}
|
|
332
|
+
}`.trim();
|
|
333
|
+
}
|
|
334
|
+
|
|
267
335
|
// src/codegen/emitters/builder.ts
|
|
268
336
|
function emitBuilderClass(info) {
|
|
269
337
|
if (!hasClassDecorator(info, "Builder")) {
|
|
@@ -363,6 +431,25 @@ function emitDeclarationModuleBlock(relSource, classes) {
|
|
|
363
431
|
if (hasClassDecorator(info, "Iterable")) {
|
|
364
432
|
augments.push(" [Symbol.iterator](): IterableIterator<unknown>;");
|
|
365
433
|
}
|
|
434
|
+
if (hasClassDecorator(info, "Composite")) {
|
|
435
|
+
augments.push(" add(child: object): void;");
|
|
436
|
+
augments.push(" remove(child: object): void;");
|
|
437
|
+
augments.push(" getChild(index: number): object;");
|
|
438
|
+
augments.push(" getChildren(): readonly object[];");
|
|
439
|
+
augments.push(" traverse(callback: (node: object) => void): void;");
|
|
440
|
+
augments.push(" [Symbol.iterator](): IterableIterator<object>;");
|
|
441
|
+
}
|
|
442
|
+
if (hasClassDecorator(info, "Wraps")) {
|
|
443
|
+
const dec = info.decorators.find((d) => d.name === "Wraps");
|
|
444
|
+
const innerName = dec?.arguments[0] ? String(dec.arguments[0]) : "unknown";
|
|
445
|
+
augments.push(` protected inner: ${innerName};`);
|
|
446
|
+
}
|
|
447
|
+
if (hasClassDecorator(info, "Visitable")) {
|
|
448
|
+
augments.push(" accept(visitor: unknown): unknown;");
|
|
449
|
+
}
|
|
450
|
+
if (hasClassDecorator(info, "TemplateMethod")) {
|
|
451
|
+
augments.push(` ${getTemplateMethodName(info)}(): void;`);
|
|
452
|
+
}
|
|
366
453
|
if (augments.length > 0) {
|
|
367
454
|
lines.push(` interface ${info.name} {`);
|
|
368
455
|
lines.push(...augments);
|
|
@@ -462,13 +549,68 @@ function emitWithFns(info) {
|
|
|
462
549
|
return fields.map((f) => emitSingleWithFn(info, f)).join("\n\n");
|
|
463
550
|
}
|
|
464
551
|
|
|
552
|
+
// src/codegen/emitters/template-method-emit.ts
|
|
553
|
+
function emitTemplateMethodFn(info) {
|
|
554
|
+
if (!hasClassDecorator(info, "TemplateMethod")) return "";
|
|
555
|
+
const templateName = getTemplateMethodName(info);
|
|
556
|
+
const steps = getTemplateMethodSteps(info);
|
|
557
|
+
const hookNames = getHookMethodNames(info);
|
|
558
|
+
for (const step of steps) {
|
|
559
|
+
if (!hookNames.includes(step)) {
|
|
560
|
+
throw new Error(`@TemplateMethod on ${info.name}: missing @Hook method for step "${step}"`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
const calls = steps.map((step) => `this.${step}();`).join(" ");
|
|
564
|
+
return `
|
|
565
|
+
function ${info.name}_${templateName}(this: ${info.name}): void {
|
|
566
|
+
${calls}
|
|
567
|
+
}`.trim();
|
|
568
|
+
}
|
|
569
|
+
function emitTemplateMethodApplyAssignment(info) {
|
|
570
|
+
if (!hasClassDecorator(info, "TemplateMethod")) return void 0;
|
|
571
|
+
const templateName = getTemplateMethodName(info);
|
|
572
|
+
return `prototype.${templateName} = ${info.name}_${templateName};`;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// src/codegen/emitters/visitor-emit.ts
|
|
576
|
+
function emitVisitableAcceptFn(info) {
|
|
577
|
+
if (!hasClassDecorator(info, "Visitable")) return "";
|
|
578
|
+
const methodName = visitMethodName(info.name);
|
|
579
|
+
return `
|
|
580
|
+
function ${info.name}_accept(this: ${info.name}, visitor: unknown): unknown {
|
|
581
|
+
const handler = (visitor as Record<string, unknown>)['${methodName}'];
|
|
582
|
+
if (typeof handler !== 'function') {
|
|
583
|
+
throw new Error(\`Visitor missing ${methodName} for ${info.name}\`);
|
|
584
|
+
}
|
|
585
|
+
return handler.call(visitor, this);
|
|
586
|
+
}`.trim();
|
|
587
|
+
}
|
|
588
|
+
function emitVisitableAcceptApplyAssignment(info) {
|
|
589
|
+
if (!hasClassDecorator(info, "Visitable")) return void 0;
|
|
590
|
+
return `prototype.accept = ${info.name}_accept;`;
|
|
591
|
+
}
|
|
592
|
+
|
|
465
593
|
// src/codegen/emitters/index.ts
|
|
466
594
|
function emitImports(classes, importPath) {
|
|
467
595
|
const names = classes.filter(hasCodegenClassDecorator).map((c) => c.name);
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
596
|
+
const productTypes = /* @__PURE__ */ new Set();
|
|
597
|
+
for (const info of classes) {
|
|
598
|
+
if (hasClassDecorator(info, "AbstractFactory")) {
|
|
599
|
+
for (const product of getAbstractFactoryProducts(info)) {
|
|
600
|
+
productTypes.add(product);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
const importLines = [];
|
|
605
|
+
if (names.length > 0) {
|
|
606
|
+
importLines.push(`import { ${names.join(", ")} } from '${importPath}';`);
|
|
607
|
+
}
|
|
608
|
+
const extraProducts = [...productTypes].filter((p) => !names.includes(p));
|
|
609
|
+
if (extraProducts.length > 0) {
|
|
610
|
+
importLines.push(`import type { ${extraProducts.join(", ")} } from '${importPath}';`);
|
|
611
|
+
}
|
|
612
|
+
if (importLines.length === 0) return "";
|
|
613
|
+
return importLines.join("\n") + "\n\n";
|
|
472
614
|
}
|
|
473
615
|
function emitToStringFn(info) {
|
|
474
616
|
if (!wantsToString(info)) return "";
|
|
@@ -507,6 +649,10 @@ function emitApplyMixin(info) {
|
|
|
507
649
|
`(ctor as typeof ${info.name} & { equals(a: ${info.name} | null | undefined, b: ${info.name} | null | undefined): boolean }).equals = ${info.name}_equalsStatic;`
|
|
508
650
|
);
|
|
509
651
|
}
|
|
652
|
+
const templateApply = emitTemplateMethodApplyAssignment(info);
|
|
653
|
+
if (templateApply) assignments.push(templateApply);
|
|
654
|
+
const visitableApply = emitVisitableAcceptApplyAssignment(info);
|
|
655
|
+
if (visitableApply) assignments.push(visitableApply);
|
|
510
656
|
if (assignments.length === 0) return "";
|
|
511
657
|
return `
|
|
512
658
|
export function apply${info.name}Generated(ctor: typeof ${info.name}): void {
|
|
@@ -540,6 +686,12 @@ function emitClassCompanionBlocks(info) {
|
|
|
540
686
|
if (delegate) blocks.push(delegate);
|
|
541
687
|
const builderFn = emitBuilderFn(info);
|
|
542
688
|
if (builderFn) blocks.push(builderFn);
|
|
689
|
+
const templateMethod = emitTemplateMethodFn(info);
|
|
690
|
+
if (templateMethod) blocks.push(templateMethod);
|
|
691
|
+
const visitableAccept = emitVisitableAcceptFn(info);
|
|
692
|
+
if (visitableAccept) blocks.push(visitableAccept);
|
|
693
|
+
const abstractFactory = emitAbstractFactoryMixin(info);
|
|
694
|
+
if (abstractFactory) blocks.push(abstractFactory);
|
|
543
695
|
const apply = emitApplyMixin(info);
|
|
544
696
|
if (apply) blocks.push(apply);
|
|
545
697
|
return blocks.filter(Boolean).join("\n\n");
|