@jxsuite/compiler 0.0.1

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.
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Compile-class.js — Compile .class.json schema-defined classes to JavaScript ES modules
3
+ *
4
+ * Generates proper ES modules with private fields, static members, accessors, etc. from a JSON
5
+ * Schema 2020-12 class definition.
6
+ */
7
+
8
+ /**
9
+ * Compile a .class.json schema to a JavaScript ES module string.
10
+ *
11
+ * @param {any} classDef - Parsed .class.json content (must have $prototype: "Class")
12
+ * @param {any} [_opts]
13
+ * @returns {string} JavaScript module source code
14
+ */
15
+ export function compileClassJson(classDef, _opts = {}) {
16
+ const className = classDef.title;
17
+ if (!className) throw new Error("compileClassJson: missing title (class name)");
18
+
19
+ const baseClass = resolveBaseClass(classDef.extends);
20
+ const fields = classDef.$defs?.fields ?? {};
21
+ const ctor = classDef.$defs?.constructor;
22
+ const methods = classDef.$defs?.methods ?? {};
23
+
24
+ /** @type {string[]} */
25
+ const lines = [];
26
+ lines.push("// Generated by @jxsuite/compiler from .class.json — do not edit manually");
27
+ if (classDef.$id) lines.push(`// Source: ${classDef.$id}`);
28
+ lines.push("");
29
+
30
+ // Class declaration
31
+ const extendsClause = baseClass !== "Object" ? ` extends ${baseClass}` : "";
32
+ lines.push(`class ${className}${extendsClause} {`);
33
+
34
+ // Static fields
35
+ for (const [key, field] of Object.entries(fields)) {
36
+ const f = /** @type {any} */ (field);
37
+ if (f.scope !== "static") continue;
38
+ const name = f.identifier ?? key;
39
+ const prefix = f.access === "private" ? "#" : "";
40
+ const initVal =
41
+ f.initializer !== undefined
42
+ ? JSON.stringify(f.initializer)
43
+ : f.default !== undefined
44
+ ? JSON.stringify(f.default)
45
+ : "null";
46
+ lines.push(` static ${prefix}${name} = ${initVal};`);
47
+ }
48
+
49
+ // Instance field declarations (private)
50
+ for (const [key, field] of Object.entries(fields)) {
51
+ const f = /** @type {any} */ (field);
52
+ if (f.scope === "static") continue;
53
+ if (f.access !== "private") continue;
54
+ const name = f.identifier ?? key;
55
+ lines.push(` #${name};`);
56
+ }
57
+
58
+ if (Object.keys(fields).length > 0) lines.push("");
59
+
60
+ // Constructor
61
+ lines.push(" constructor(config = {}) {");
62
+ if (ctor?.superCall || baseClass !== "Object") {
63
+ const superArgs = ctor?.superCall?.arguments ? ctor.superCall.arguments.join(", ") : "";
64
+ lines.push(` super(${superArgs});`);
65
+ }
66
+
67
+ // Initialize instance fields from config or defaults
68
+ for (const [key, field] of Object.entries(fields)) {
69
+ const f = /** @type {any} */ (field);
70
+ if (f.scope === "static") continue;
71
+ const name = f.identifier ?? key;
72
+ const prefix = f.access === "private" ? "#" : "";
73
+ const initVal =
74
+ f.initializer !== undefined
75
+ ? JSON.stringify(f.initializer)
76
+ : f.default !== undefined
77
+ ? JSON.stringify(f.default)
78
+ : "null";
79
+ lines.push(
80
+ ` this.${prefix}${name} = config.${name} !== undefined ? config.${name} : ${initVal};`,
81
+ );
82
+ }
83
+
84
+ // Constructor body statements
85
+ if (ctor?.body) {
86
+ const bodyLines = Array.isArray(ctor.body) ? ctor.body : [ctor.body];
87
+ for (const line of bodyLines) {
88
+ lines.push(` ${line}`);
89
+ }
90
+ }
91
+ lines.push(" }");
92
+
93
+ // Methods
94
+ for (const [key, method] of Object.entries(methods)) {
95
+ const m = /** @type {any} */ (method);
96
+ const name = m.identifier ?? key;
97
+ const isStatic = m.scope === "static";
98
+ const isPrivate = m.access === "private";
99
+ const prefix = isPrivate ? "#" : "";
100
+ const staticPrefix = isStatic ? "static " : "";
101
+ const asyncPrefix = m.returnType?.$ref?.includes("Promise") || isMethodAsync(m) ? "async " : "";
102
+
103
+ const params = resolveParams(m.parameters ?? []);
104
+ const bodyStr = resolveBody(m.body);
105
+
106
+ lines.push("");
107
+
108
+ if (m.role === "accessor") {
109
+ // Getter
110
+ if (m.getter) {
111
+ lines.push(` ${staticPrefix}get ${prefix}${name}() {`);
112
+ lines.push(` ${m.getter.body}`);
113
+ lines.push(" }");
114
+ }
115
+ // Setter
116
+ if (m.setter) {
117
+ const setterParams = resolveParams(m.setter.parameters ?? []);
118
+ lines.push(` ${staticPrefix}set ${prefix}${name}(${setterParams}) {`);
119
+ lines.push(` ${m.setter.body}`);
120
+ lines.push(" }");
121
+ }
122
+ } else {
123
+ lines.push(` ${staticPrefix}${asyncPrefix}${prefix}${name}(${params}) {`);
124
+ for (const line of bodyStr) {
125
+ lines.push(` ${line}`);
126
+ }
127
+ lines.push(" }");
128
+ }
129
+ }
130
+
131
+ lines.push("}");
132
+ lines.push("");
133
+ lines.push(`export { ${className} };`);
134
+ lines.push(`export default ${className};`);
135
+ lines.push("");
136
+
137
+ return lines.join("\n");
138
+ }
139
+
140
+ /**
141
+ * Resolve the base class name from an extends value.
142
+ *
143
+ * @param {any} ext
144
+ * @returns {string}
145
+ */
146
+ function resolveBaseClass(ext) {
147
+ if (!ext) return "Object";
148
+ if (typeof ext === "string") return ext;
149
+ // { $ref: "./Parent.class.json" } — extract title from filename as best guess
150
+ if (ext.$ref) {
151
+ const ref = ext.$ref;
152
+ const match = ref.match(/([A-Za-z0-9_]+)\.class\.json/);
153
+ return match ? match[1] : "Object";
154
+ }
155
+ return "Object";
156
+ }
157
+
158
+ /**
159
+ * Resolve parameter names from $ref or inline definitions.
160
+ *
161
+ * @param {any[]} params
162
+ * @returns {string}
163
+ */
164
+ function resolveParams(params) {
165
+ return params
166
+ .map((/** @type {any} */ p) => {
167
+ if (p.$ref) return p.$ref.split("/").pop();
168
+ return p.identifier ?? p.name ?? "arg";
169
+ })
170
+ .join(", ");
171
+ }
172
+
173
+ /**
174
+ * Resolve body from string or array of strings.
175
+ *
176
+ * @param {any} body
177
+ * @returns {string[]}
178
+ */
179
+ function resolveBody(body) {
180
+ if (!body) return [""];
181
+ if (Array.isArray(body)) return body;
182
+ return [body];
183
+ }
184
+
185
+ /**
186
+ * Simple heuristic: does the method body contain await?
187
+ *
188
+ * @param {any} method
189
+ * @returns {boolean}
190
+ */
191
+ function isMethodAsync(method) {
192
+ const body = Array.isArray(method.body) ? method.body.join("\n") : (method.body ?? "");
193
+ return body.includes("await ");
194
+ }