lombok-typescript 0.7.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 +18 -6
- package/dist/cli/index.cjs +149 -5
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +149 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/codegen/index.cjs +149 -5
- package/dist/codegen/index.cjs.map +1 -1
- package/dist/codegen/index.js +149 -5
- package/dist/codegen/index.js.map +1 -1
- package/dist/legacy/index.cjs +106 -0
- package/dist/legacy/index.cjs.map +1 -1
- package/dist/legacy/index.d.cts +18 -3
- package/dist/legacy/index.d.ts +18 -3
- package/dist/legacy/index.js +100 -1
- package/dist/legacy/index.js.map +1 -1
- package/dist/stage3/index.cjs +118 -1
- package/dist/stage3/index.cjs.map +1 -1
- package/dist/stage3/index.d.cts +18 -3
- package/dist/stage3/index.d.ts +18 -3
- package/dist/stage3/index.js +112 -2
- package/dist/stage3/index.js.map +1 -1
- package/dist/{proxy-BJ6_DDQ0.d.cts → visitor-B5ByGIWE.d.cts} +17 -1
- package/dist/{proxy-BQ3WvvT_.d.ts → visitor-BqARKwCX.d.ts} +17 -1
- package/package.json +2 -2
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,12 +69,17 @@ npm install lombok-typescript@preview
|
|
|
62
69
|
- @Observable
|
|
63
70
|
- @ChainOfResponsibility
|
|
64
71
|
- @Iterable
|
|
72
|
+
- @Visitor
|
|
73
|
+
- @Visitable
|
|
74
|
+
- @Hook
|
|
65
75
|
|
|
66
76
|
### Structural patterns
|
|
67
77
|
|
|
68
78
|
- @Flyweight
|
|
69
79
|
- @Proxy
|
|
70
80
|
- @Composite
|
|
81
|
+
- @Wraps
|
|
82
|
+
- @TemplateMethod
|
|
71
83
|
|
|
72
84
|
### TypeScript utilities
|
|
73
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")) {
|
|
@@ -371,6 +439,17 @@ function emitDeclarationModuleBlock(relSource, classes) {
|
|
|
371
439
|
augments.push(" traverse(callback: (node: object) => void): void;");
|
|
372
440
|
augments.push(" [Symbol.iterator](): IterableIterator<object>;");
|
|
373
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
|
+
}
|
|
374
453
|
if (augments.length > 0) {
|
|
375
454
|
lines.push(` interface ${info.name} {`);
|
|
376
455
|
lines.push(...augments);
|
|
@@ -470,13 +549,68 @@ function emitWithFns(info) {
|
|
|
470
549
|
return fields.map((f) => emitSingleWithFn(info, f)).join("\n\n");
|
|
471
550
|
}
|
|
472
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
|
+
|
|
473
593
|
// src/codegen/emitters/index.ts
|
|
474
594
|
function emitImports(classes, importPath) {
|
|
475
595
|
const names = classes.filter(hasCodegenClassDecorator).map((c) => c.name);
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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";
|
|
480
614
|
}
|
|
481
615
|
function emitToStringFn(info) {
|
|
482
616
|
if (!wantsToString(info)) return "";
|
|
@@ -515,6 +649,10 @@ function emitApplyMixin(info) {
|
|
|
515
649
|
`(ctor as typeof ${info.name} & { equals(a: ${info.name} | null | undefined, b: ${info.name} | null | undefined): boolean }).equals = ${info.name}_equalsStatic;`
|
|
516
650
|
);
|
|
517
651
|
}
|
|
652
|
+
const templateApply = emitTemplateMethodApplyAssignment(info);
|
|
653
|
+
if (templateApply) assignments.push(templateApply);
|
|
654
|
+
const visitableApply = emitVisitableAcceptApplyAssignment(info);
|
|
655
|
+
if (visitableApply) assignments.push(visitableApply);
|
|
518
656
|
if (assignments.length === 0) return "";
|
|
519
657
|
return `
|
|
520
658
|
export function apply${info.name}Generated(ctor: typeof ${info.name}): void {
|
|
@@ -548,6 +686,12 @@ function emitClassCompanionBlocks(info) {
|
|
|
548
686
|
if (delegate) blocks.push(delegate);
|
|
549
687
|
const builderFn = emitBuilderFn(info);
|
|
550
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);
|
|
551
695
|
const apply = emitApplyMixin(info);
|
|
552
696
|
if (apply) blocks.push(apply);
|
|
553
697
|
return blocks.filter(Boolean).join("\n\n");
|