@atscript/typescript 0.1.49 → 0.1.50
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 +9 -6
- package/dist/index.cjs +4 -1480
- package/dist/index.mjs +2 -1454
- package/dist/plugin-D96fH7Va.mjs +1456 -0
- package/dist/plugin-cY7VLpmN.cjs +1491 -0
- package/dist/test-utils.cjs +27 -0
- package/dist/test-utils.d.ts +25 -0
- package/dist/test-utils.mjs +26 -0
- package/dist/utils.cjs +38 -22
- package/dist/utils.d.ts +10 -6
- package/dist/utils.mjs +38 -22
- package/package.json +18 -12
- package/scripts/setup-skills.js +0 -88
- package/skills/atscript-typescript/.gitkeep +0 -0
- package/skills/atscript-typescript/SKILL.md +0 -52
- package/skills/atscript-typescript/annotations.md +0 -259
- package/skills/atscript-typescript/codegen.md +0 -131
- package/skills/atscript-typescript/core.md +0 -166
- package/skills/atscript-typescript/runtime.md +0 -290
- package/skills/atscript-typescript/syntax.md +0 -252
- package/skills/atscript-typescript/utilities.md +0 -452
- package/skills/atscript-typescript/validation.md +0 -293
- /package/dist/{json-schema-Bu4xgpQn.cjs → json-schema-BgW_S2sP.cjs} +0 -0
- /package/dist/{json-schema-Bl8jkrCj.mjs → json-schema-DrJMwvm1.mjs} +0 -0
package/dist/index.mjs
CHANGED
|
@@ -1,1456 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import { DEFAULT_FORMAT, flattenInterfaceNode, hasNavPropAnnotation, isArray, isConst, isGroup, isInterface, isPrimitive, isQueryLogical, isRef, isStructure } from "@atscript/core";
|
|
1
|
+
import { tsPlugin } from "./plugin-D96fH7Va.mjs";
|
|
2
|
+
import "./json-schema-DrJMwvm1.mjs";
|
|
4
3
|
|
|
5
|
-
//#region packages/typescript/src/codegen/code-printer.ts
|
|
6
|
-
function _define_property$3(obj, key, value) {
|
|
7
|
-
if (key in obj) Object.defineProperty(obj, key, {
|
|
8
|
-
value,
|
|
9
|
-
enumerable: true,
|
|
10
|
-
configurable: true,
|
|
11
|
-
writable: true
|
|
12
|
-
});
|
|
13
|
-
else obj[key] = value;
|
|
14
|
-
return obj;
|
|
15
|
-
}
|
|
16
|
-
var CodePrinter = class {
|
|
17
|
-
/**
|
|
18
|
-
* Write text with NO automatic newline.
|
|
19
|
-
* If current line is empty, we prepend indentation.
|
|
20
|
-
*/ write(...text) {
|
|
21
|
-
if (!this.currentLine) this.currentLine = this.indentString();
|
|
22
|
-
this.currentLine += text.join("");
|
|
23
|
-
return this;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Write text and then finalize this line with a newline.
|
|
27
|
-
* Implemented by calling `write(...)` + push line + reset `currentLine`.
|
|
28
|
-
*/ writeln(...text) {
|
|
29
|
-
this.write(...text);
|
|
30
|
-
this.lines.push(this.currentLine);
|
|
31
|
-
this.currentLine = "";
|
|
32
|
-
return this;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Open a block with an opening/closing pair, e.g. "{}" or "()".
|
|
36
|
-
* By default, places the opening delimiter on the current line (no extra newline).
|
|
37
|
-
*/ block(pair) {
|
|
38
|
-
return this.doBlock(pair, false);
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Same as `block()`, but adds a newline after writing the opening delimiter.
|
|
42
|
-
*/ blockln(pair) {
|
|
43
|
-
return this.doBlock(pair, true);
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Close the most recent block. By default, closing delimiter goes on the current line.
|
|
47
|
-
*/ pop() {
|
|
48
|
-
return this.doPop(false);
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Same as `pop()`, but puts the closing delimiter on a new line.
|
|
52
|
-
*/ popln() {
|
|
53
|
-
return this.doPop(true);
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Increase indentation by one level.
|
|
57
|
-
*/ indent() {
|
|
58
|
-
this.indentLevel++;
|
|
59
|
-
return this;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Decrease indentation by one level (never below 0).
|
|
63
|
-
*/ unindent() {
|
|
64
|
-
this.indentLevel = Math.max(0, this.indentLevel - 1);
|
|
65
|
-
return this;
|
|
66
|
-
}
|
|
67
|
-
render() {
|
|
68
|
-
return this.toString();
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Return the entire code as a string. Removes any half-finished current line if empty.
|
|
72
|
-
*/ toString() {
|
|
73
|
-
if (this.currentLine.trim() !== "") this.lines.push(this.currentLine);
|
|
74
|
-
else if (this.currentLine.length > 0) this.lines.push(this.currentLine);
|
|
75
|
-
return this.lines.join("\n");
|
|
76
|
-
}
|
|
77
|
-
doBlock(pair, withNewLine) {
|
|
78
|
-
if (pair.length < 2) throw new Error(`Block pair must have at least 2 chars, e.g. "{}" or "()". Got "${pair}"`);
|
|
79
|
-
const opening = pair[0];
|
|
80
|
-
const closing = pair[pair.length - 1];
|
|
81
|
-
this.write(opening);
|
|
82
|
-
this.blockStack.push(closing);
|
|
83
|
-
if (withNewLine) this.writeln();
|
|
84
|
-
this.indent();
|
|
85
|
-
return this;
|
|
86
|
-
}
|
|
87
|
-
doPop(withNewLine) {
|
|
88
|
-
if (this.blockStack.length === 0) throw new Error("No block to pop (stack is empty).");
|
|
89
|
-
const closing = this.blockStack.pop();
|
|
90
|
-
this.unindent();
|
|
91
|
-
if (withNewLine) this.writeln(closing);
|
|
92
|
-
else this.write(closing);
|
|
93
|
-
return this;
|
|
94
|
-
}
|
|
95
|
-
indentString() {
|
|
96
|
-
return " ".repeat(this.indentLevel * this.indentSize);
|
|
97
|
-
}
|
|
98
|
-
constructor() {
|
|
99
|
-
_define_property$3(this, "lines", []);
|
|
100
|
-
_define_property$3(this, "currentLine", "");
|
|
101
|
-
_define_property$3(this, "indentLevel", 0);
|
|
102
|
-
_define_property$3(this, "indentSize", 2);
|
|
103
|
-
_define_property$3(this, "blockStack", []);
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
//#endregion
|
|
108
|
-
//#region packages/typescript/src/codegen/base-renderer.ts
|
|
109
|
-
function _define_property$2(obj, key, value) {
|
|
110
|
-
if (key in obj) Object.defineProperty(obj, key, {
|
|
111
|
-
value,
|
|
112
|
-
enumerable: true,
|
|
113
|
-
configurable: true,
|
|
114
|
-
writable: true
|
|
115
|
-
});
|
|
116
|
-
else obj[key] = value;
|
|
117
|
-
return obj;
|
|
118
|
-
}
|
|
119
|
-
var BaseRenderer = class extends CodePrinter {
|
|
120
|
-
get unused() {
|
|
121
|
-
return this._unused ?? (this._unused = new Set(this.doc.getUnusedTokens().map((t) => t.text)));
|
|
122
|
-
}
|
|
123
|
-
pre() {}
|
|
124
|
-
post() {}
|
|
125
|
-
render() {
|
|
126
|
-
this.pre();
|
|
127
|
-
for (const node of this.doc.nodes) this.renderNode(node);
|
|
128
|
-
this.post();
|
|
129
|
-
return this.toString();
|
|
130
|
-
}
|
|
131
|
-
renderInterface(node) {}
|
|
132
|
-
renderType(node) {}
|
|
133
|
-
renderAnnotate(node) {}
|
|
134
|
-
transformFromPath(path$1) {
|
|
135
|
-
return `${path$1}.as`;
|
|
136
|
-
}
|
|
137
|
-
renderImport(node) {
|
|
138
|
-
const def = node.getDefinition();
|
|
139
|
-
const refs = [];
|
|
140
|
-
const from = this.transformFromPath(node.token("path").text);
|
|
141
|
-
let isUnusedImport = true;
|
|
142
|
-
if (isGroup(def)) {
|
|
143
|
-
for (const child of def.unwrap()) if (isRef(child) && !this.unused.has(child.id)) {
|
|
144
|
-
const node$1 = child;
|
|
145
|
-
refs.push(node$1.id);
|
|
146
|
-
isUnusedImport = false;
|
|
147
|
-
}
|
|
148
|
-
} else if (isRef(def) && !this.unused.has(def.id)) {
|
|
149
|
-
refs.push(def.id);
|
|
150
|
-
isUnusedImport = false;
|
|
151
|
-
}
|
|
152
|
-
if (!isUnusedImport) this.writeln(`import { ${refs.join(", ")} } from "${from}"`);
|
|
153
|
-
}
|
|
154
|
-
renderNode(node) {
|
|
155
|
-
switch (node.entity) {
|
|
156
|
-
case "interface": {
|
|
157
|
-
this.renderInterface(node);
|
|
158
|
-
break;
|
|
159
|
-
}
|
|
160
|
-
case "type": {
|
|
161
|
-
this.renderType(node);
|
|
162
|
-
break;
|
|
163
|
-
}
|
|
164
|
-
case "import": {
|
|
165
|
-
this.renderImport(node);
|
|
166
|
-
break;
|
|
167
|
-
}
|
|
168
|
-
case "annotate": {
|
|
169
|
-
this.renderAnnotate(node);
|
|
170
|
-
break;
|
|
171
|
-
}
|
|
172
|
-
default: break;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
constructor(doc) {
|
|
176
|
-
super(), _define_property$2(this, "doc", void 0), _define_property$2(this, "_unused", void 0), this.doc = doc;
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
//#endregion
|
|
181
|
-
//#region packages/typescript/src/codegen/utils.ts
|
|
182
|
-
const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
183
|
-
function wrapProp(name) {
|
|
184
|
-
if (!validIdentifier.test(name)) return `"${escapeQuotes(name)}"`;
|
|
185
|
-
return name;
|
|
186
|
-
}
|
|
187
|
-
function escapeQuotes(str) {
|
|
188
|
-
return str.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
//#endregion
|
|
192
|
-
//#region packages/typescript/src/codegen/type-renderer.ts
|
|
193
|
-
function _define_property$1(obj, key, value) {
|
|
194
|
-
if (key in obj) Object.defineProperty(obj, key, {
|
|
195
|
-
value,
|
|
196
|
-
enumerable: true,
|
|
197
|
-
configurable: true,
|
|
198
|
-
writable: true
|
|
199
|
-
});
|
|
200
|
-
else obj[key] = value;
|
|
201
|
-
return obj;
|
|
202
|
-
}
|
|
203
|
-
var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
204
|
-
pre() {
|
|
205
|
-
this.writeln("// prettier-ignore-start");
|
|
206
|
-
this.writeln("/* eslint-disable */");
|
|
207
|
-
this.writeln("/* oxlint-disable */");
|
|
208
|
-
this.writeln(`/// <reference path="./${this.doc.name}" />`);
|
|
209
|
-
this.writeln("/**");
|
|
210
|
-
this.writeln(" * 🪄 This file was generated by Atscript");
|
|
211
|
-
this.writeln(" * Do not edit this file!");
|
|
212
|
-
this.writeln(" */");
|
|
213
|
-
this.writeln();
|
|
214
|
-
this.writeln("import type { TAtscriptTypeObject, TAtscriptTypeComplex, TAtscriptTypeFinal, TAtscriptTypeArray, TAtscriptAnnotatedType, TMetadataMap, Validator, TValidatorOptions } from \"@atscript/typescript/utils\"");
|
|
215
|
-
}
|
|
216
|
-
post() {
|
|
217
|
-
this.writeln("// prettier-ignore-end");
|
|
218
|
-
}
|
|
219
|
-
renderTypeDefString(def) {
|
|
220
|
-
const newThis = new TypeRenderer(this.doc, this.opts);
|
|
221
|
-
newThis.renderTypeDef(def);
|
|
222
|
-
return newThis.toString();
|
|
223
|
-
}
|
|
224
|
-
renderTypeDef(def) {
|
|
225
|
-
if (!def) {
|
|
226
|
-
this.write("unknown");
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
if (isStructure(def)) return this.renderStructure(def);
|
|
230
|
-
if (isGroup(def)) {
|
|
231
|
-
const tuple = def.entity === "tuple";
|
|
232
|
-
const operator = tuple ? ", " : ` ${def.op} `;
|
|
233
|
-
const children = def.unwrap();
|
|
234
|
-
const hasSubgroup = !tuple && children.some((c) => c.entity === "group");
|
|
235
|
-
this.write(tuple ? "[" : hasSubgroup ? "(" : "");
|
|
236
|
-
this.renderTypeDef(def.first);
|
|
237
|
-
for (const child of children.slice(1)) {
|
|
238
|
-
this.write(operator);
|
|
239
|
-
this.renderTypeDef(child);
|
|
240
|
-
}
|
|
241
|
-
return this.write(tuple ? "]" : hasSubgroup ? ")" : "");
|
|
242
|
-
}
|
|
243
|
-
if (isConst(def)) {
|
|
244
|
-
const name = def.token("identifier")?.type === "number" ? def.id : `"${escapeQuotes(def.id)}"`;
|
|
245
|
-
return this.write(name);
|
|
246
|
-
}
|
|
247
|
-
if (isRef(def)) {
|
|
248
|
-
const node = def;
|
|
249
|
-
const unwound = this.doc.unwindType(node.id, node.chain);
|
|
250
|
-
if (isPrimitive(unwound?.def)) {
|
|
251
|
-
this.write(renderPrimitiveTypeDef(unwound.def.config.type));
|
|
252
|
-
if (node.hasChain) this.write(` /* ${node.chain.map((c) => c.text).join(".")} */`);
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
let name = node.id;
|
|
256
|
-
for (const c of node.chain) name += `["${escapeQuotes(c.text)}"]`;
|
|
257
|
-
return this.write(name);
|
|
258
|
-
}
|
|
259
|
-
if (isArray(def)) {
|
|
260
|
-
const node = def;
|
|
261
|
-
const def2 = node.getDefinition();
|
|
262
|
-
const isGrp = def2?.entity === "group";
|
|
263
|
-
if (isGrp) this.write("(");
|
|
264
|
-
this.renderTypeDef(def2);
|
|
265
|
-
if (isGrp) this.write(")");
|
|
266
|
-
return this.write("[]");
|
|
267
|
-
}
|
|
268
|
-
if (isPrimitive(def)) return this.write(renderPrimitiveTypeDef(def.config.type));
|
|
269
|
-
}
|
|
270
|
-
renderStructure(struct, asClass, interfaceNode, filterProps) {
|
|
271
|
-
this.blockln("{}");
|
|
272
|
-
const patterns = [];
|
|
273
|
-
const propsDefs = new Set();
|
|
274
|
-
for (const prop of struct.props.values()) {
|
|
275
|
-
if (filterProps?.has(prop.id)) continue;
|
|
276
|
-
if (prop.token("identifier")?.pattern) {
|
|
277
|
-
if (!filterProps) patterns.push(prop);
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
const phantomType = this.phantomPropType(prop.getDefinition());
|
|
281
|
-
if (phantomType) {
|
|
282
|
-
this.writeln(`// ${prop.id}: ${phantomType}`);
|
|
283
|
-
continue;
|
|
284
|
-
}
|
|
285
|
-
const optional = !!prop.token("optional");
|
|
286
|
-
this.write(wrapProp(prop.id), optional ? "?" : "", ": ");
|
|
287
|
-
const renderedDef = this.renderTypeDefString(prop.getDefinition());
|
|
288
|
-
propsDefs.add(renderedDef);
|
|
289
|
-
renderedDef.split("\n").forEach((l) => this.writeln(l));
|
|
290
|
-
}
|
|
291
|
-
if (patterns.length > 0) {
|
|
292
|
-
this.write(`[key: string]: `);
|
|
293
|
-
for (const prop of patterns) propsDefs.add(this.renderTypeDefString(prop.getDefinition()));
|
|
294
|
-
const defs = Array.from(propsDefs);
|
|
295
|
-
if (defs.length > 1) {
|
|
296
|
-
this.indent();
|
|
297
|
-
for (const def of defs) {
|
|
298
|
-
this.writeln();
|
|
299
|
-
this.write("| ");
|
|
300
|
-
def.split("\n").forEach((l) => this.write(l.trim()));
|
|
301
|
-
}
|
|
302
|
-
this.unindent();
|
|
303
|
-
this.writeln();
|
|
304
|
-
} else defs[0].split("\n").forEach((l) => this.writeln(l));
|
|
305
|
-
}
|
|
306
|
-
if (asClass) this.renderStaticDeclarations(asClass, interfaceNode);
|
|
307
|
-
this.pop();
|
|
308
|
-
}
|
|
309
|
-
renderStaticDeclarations(asClass, interfaceNode) {
|
|
310
|
-
this.writeln("static __is_atscript_annotated_type: true");
|
|
311
|
-
this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}, ${asClass}>`);
|
|
312
|
-
this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
|
|
313
|
-
this.writeln(`static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${asClass}>`);
|
|
314
|
-
if (resolveJsonSchemaMode(this.opts) === false) this.writeln("/** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */");
|
|
315
|
-
this.writeln("static toJsonSchema: () => any");
|
|
316
|
-
if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
|
|
317
|
-
this.writeln("static toExampleData?: () => any");
|
|
318
|
-
if (interfaceNode && this.hasDbEntity(interfaceNode)) {
|
|
319
|
-
this.renderFlat(interfaceNode);
|
|
320
|
-
this.renderOwnProps(interfaceNode);
|
|
321
|
-
this.renderNavProps(interfaceNode);
|
|
322
|
-
this.renderPk(interfaceNode);
|
|
323
|
-
this.renderDimMeasure(interfaceNode);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
renderInterface(node) {
|
|
327
|
-
this.writeln();
|
|
328
|
-
const exported = node.token("export")?.text === "export";
|
|
329
|
-
this.renderJsDoc(node);
|
|
330
|
-
this.write(exported ? "export declare " : "declare ");
|
|
331
|
-
if (node.hasExtends) {
|
|
332
|
-
const firstParent = node.extendsTokens[0].text;
|
|
333
|
-
this.write(`class ${node.id} extends ${firstParent} `);
|
|
334
|
-
const resolved = this.doc.resolveInterfaceExtends(node);
|
|
335
|
-
if (resolved?.entity === "structure") {
|
|
336
|
-
const firstParentUnwound = this.doc.unwindType(firstParent);
|
|
337
|
-
let firstParentProps;
|
|
338
|
-
if (firstParentUnwound?.def) {
|
|
339
|
-
let fpDef = firstParentUnwound.def;
|
|
340
|
-
if (isInterface(fpDef)) {
|
|
341
|
-
if (fpDef.hasExtends) {
|
|
342
|
-
const fpResolved = firstParentUnwound.doc.resolveInterfaceExtends(fpDef);
|
|
343
|
-
if (fpResolved && isStructure(fpResolved)) firstParentProps = fpResolved.props;
|
|
344
|
-
}
|
|
345
|
-
if (!firstParentProps) fpDef = fpDef.getDefinition() || fpDef;
|
|
346
|
-
}
|
|
347
|
-
if (!firstParentProps && isStructure(fpDef)) firstParentProps = fpDef.props;
|
|
348
|
-
}
|
|
349
|
-
this.renderStructure(resolved, node.id, node, firstParentProps);
|
|
350
|
-
} else this.writeln("{}");
|
|
351
|
-
} else {
|
|
352
|
-
this.write(`class ${node.id} `);
|
|
353
|
-
const struct = node.getDefinition();
|
|
354
|
-
if (struct?.entity === "structure") this.renderStructure(struct, node.id, node);
|
|
355
|
-
else this.writeln("{}");
|
|
356
|
-
}
|
|
357
|
-
this.writeln();
|
|
358
|
-
}
|
|
359
|
-
renderType(node) {
|
|
360
|
-
this.writeln();
|
|
361
|
-
const exported = node.token("export")?.text === "export";
|
|
362
|
-
this.renderJsDoc(node);
|
|
363
|
-
this.write(exported ? "export " : "declare ");
|
|
364
|
-
this.write(`type ${node.id} = `);
|
|
365
|
-
this.renderTypeDef(node.getDefinition());
|
|
366
|
-
this.writeln();
|
|
367
|
-
this.renderTypeNamespace(node);
|
|
368
|
-
}
|
|
369
|
-
renderAnnotate(node) {
|
|
370
|
-
if (node.isMutating) return;
|
|
371
|
-
const targetName = node.targetName;
|
|
372
|
-
this.writeln();
|
|
373
|
-
const exported = node.token("export")?.text === "export";
|
|
374
|
-
this.renderJsDoc(node);
|
|
375
|
-
if (this.isTypeTarget(targetName)) {
|
|
376
|
-
this.write(exported ? "export " : "declare ");
|
|
377
|
-
this.write(`type ${node.id} = ${targetName}`);
|
|
378
|
-
this.writeln();
|
|
379
|
-
const unwound = this.doc.unwindType(targetName);
|
|
380
|
-
this.renderTypeNamespaceFor(node.id, unwound?.def);
|
|
381
|
-
} else {
|
|
382
|
-
this.write(exported ? "export declare " : "declare ");
|
|
383
|
-
this.writeln(`class ${node.id} extends ${targetName} {}`);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
renderTypeNamespace(node) {
|
|
387
|
-
this.renderTypeNamespaceFor(node.id, node.getDefinition());
|
|
388
|
-
}
|
|
389
|
-
renderTypeNamespaceFor(name, inputDef) {
|
|
390
|
-
this.write(`declare namespace ${name} `);
|
|
391
|
-
this.blockln("{}");
|
|
392
|
-
let typeDef = "TAtscriptTypeDef";
|
|
393
|
-
if (inputDef) {
|
|
394
|
-
let realDef = inputDef;
|
|
395
|
-
if (isRef(inputDef)) realDef = this.doc.unwindType(inputDef.id, inputDef.chain)?.def || realDef;
|
|
396
|
-
realDef = this.doc.mergeIntersection(realDef);
|
|
397
|
-
if (isStructure(realDef) || isInterface(realDef)) typeDef = `TAtscriptTypeObject<keyof ${name}, ${name}>`;
|
|
398
|
-
else if (isGroup(realDef)) typeDef = `TAtscriptTypeComplex<${name}>`;
|
|
399
|
-
else if (isArray(realDef)) typeDef = `TAtscriptTypeArray<${name}>`;
|
|
400
|
-
else if (isPrimitive(realDef)) typeDef = `TAtscriptTypeFinal<${name}>`;
|
|
401
|
-
}
|
|
402
|
-
this.writeln(`const __is_atscript_annotated_type: true`);
|
|
403
|
-
this.writeln(`const type: ${typeDef}`);
|
|
404
|
-
this.writeln(`const metadata: TMetadataMap<AtscriptMetadata>`);
|
|
405
|
-
this.writeln(`const validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${name}>`);
|
|
406
|
-
if (resolveJsonSchemaMode(this.opts) === false) this.writeln("/** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */");
|
|
407
|
-
this.writeln("const toJsonSchema: () => any");
|
|
408
|
-
if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
|
|
409
|
-
this.writeln("const toExampleData: (() => any) | undefined");
|
|
410
|
-
this.popln();
|
|
411
|
-
}
|
|
412
|
-
/**
|
|
413
|
-
* Checks whether an interface is a DB entity (`@db.table` or `@db.view`).
|
|
414
|
-
*
|
|
415
|
-
* Only DB entities get `__flat`, `__pk`, `__ownProps`, and `__navProps` static properties.
|
|
416
|
-
* These exist solely to improve type-safety for filter expressions, `$select`/`$sort`,
|
|
417
|
-
* and `$with` operations in the DB layer.
|
|
418
|
-
*/ hasDbEntity(node) {
|
|
419
|
-
return !!node.annotations?.some((a) => a.name === "db.table" || a.name === "db.view" || a.name === "db.view.for");
|
|
420
|
-
}
|
|
421
|
-
/**
|
|
422
|
-
* Renders the `static __flat` property — a map of all dot-notation paths
|
|
423
|
-
* to their TypeScript value types.
|
|
424
|
-
*
|
|
425
|
-
* This enables type-safe autocomplete for filter keys and `$select`/`$sort`
|
|
426
|
-
* paths when using `AtscriptDbTable`. The `FlatOf<T>` utility type extracts
|
|
427
|
-
* this map at the type level.
|
|
428
|
-
*
|
|
429
|
-
* Special rendering rules:
|
|
430
|
-
* - **Intermediate paths** (structures, arrays of structures) → `never`
|
|
431
|
-
* (prevents `$eq` comparisons, but allows `$select` and `$exists`)
|
|
432
|
-
* - **`@db.json` fields** → `string` (stored as serialized JSON in DB,
|
|
433
|
-
* not individually queryable)
|
|
434
|
-
* - **Leaf fields** → their original TypeScript type
|
|
435
|
-
*/ renderFlat(node) {
|
|
436
|
-
this.renderFlatMap("__flat", flattenInterfaceNode(this.doc, node));
|
|
437
|
-
}
|
|
438
|
-
/**
|
|
439
|
-
* Renders the `static __ownProps` property — table-owned fields only (no nav props).
|
|
440
|
-
*/ renderOwnProps(node) {
|
|
441
|
-
this.renderFlatMap("__ownProps", flattenInterfaceNode(this.doc, node, { skipNavProps: true }), {
|
|
442
|
-
leadingNewline: true,
|
|
443
|
-
trailingNewline: true
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
/**
|
|
447
|
-
* Shared renderer for flat-map static properties (`__flat`, `__ownProps`).
|
|
448
|
-
*
|
|
449
|
-
* Special rendering rules:
|
|
450
|
-
* - **Intermediate paths** (structures, arrays of structures) → `never`
|
|
451
|
-
* - **`@db.json` fields** → `string` (stored as serialized JSON in DB)
|
|
452
|
-
* - **Leaf fields** → their original TypeScript type
|
|
453
|
-
*/ renderFlatMap(propName, flatMap, opts) {
|
|
454
|
-
if (flatMap.size === 0) return;
|
|
455
|
-
if (opts?.leadingNewline) this.writeln();
|
|
456
|
-
this.write(`static ${propName}: `);
|
|
457
|
-
this.blockln("{}");
|
|
458
|
-
for (const [path$1, descriptor] of flatMap) {
|
|
459
|
-
this.write(`"${escapeQuotes(path$1)}"`);
|
|
460
|
-
if (descriptor.optional) this.write("?");
|
|
461
|
-
this.write(": ");
|
|
462
|
-
if (descriptor.intermediate) this.writeln("never");
|
|
463
|
-
else if (descriptor.dbJson) this.writeln("string");
|
|
464
|
-
else {
|
|
465
|
-
const originalDef = descriptor.propNode?.getDefinition();
|
|
466
|
-
const defToRender = originalDef && !(isGroup(descriptor.def) && descriptor.def !== originalDef) ? originalDef : descriptor.def;
|
|
467
|
-
const renderedDef = this.renderTypeDefString(defToRender);
|
|
468
|
-
renderedDef.split("\n").forEach((l) => this.writeln(l));
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
if (opts?.trailingNewline) this.popln();
|
|
472
|
-
else this.pop();
|
|
473
|
-
}
|
|
474
|
-
/**
|
|
475
|
-
* Renders the `static __navProps` property — a map of navigation property names
|
|
476
|
-
* to their declared TypeScript types.
|
|
477
|
-
*
|
|
478
|
-
* This enables type-safe `$with` in queries: `name` is constrained to known
|
|
479
|
-
* navigation property keys, and nested filter/controls are typed to the target entity.
|
|
480
|
-
*/ renderNavProps(node) {
|
|
481
|
-
let struct;
|
|
482
|
-
if (node.hasExtends) struct = this.doc.resolveInterfaceExtends(node);
|
|
483
|
-
if (!struct) struct = node.getDefinition();
|
|
484
|
-
if (!struct || !isStructure(struct)) return;
|
|
485
|
-
const structNode = struct;
|
|
486
|
-
const navProps = [];
|
|
487
|
-
for (const [name, prop] of structNode.props) {
|
|
488
|
-
if (prop.token("identifier")?.pattern) continue;
|
|
489
|
-
if (hasNavPropAnnotation(prop)) navProps.push({
|
|
490
|
-
name,
|
|
491
|
-
prop
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
if (navProps.length === 0) return;
|
|
495
|
-
this.writeln();
|
|
496
|
-
this.write("static __navProps: ");
|
|
497
|
-
this.blockln("{}");
|
|
498
|
-
for (const { name, prop } of navProps) {
|
|
499
|
-
this.write(`"${escapeQuotes(name)}"`);
|
|
500
|
-
if (prop.token("optional")) this.write("?");
|
|
501
|
-
this.write(": ");
|
|
502
|
-
const propDef = prop.getDefinition();
|
|
503
|
-
if (propDef) {
|
|
504
|
-
const renderedDef = this.renderTypeDefString(propDef);
|
|
505
|
-
renderedDef.split("\n").forEach((l) => this.writeln(l));
|
|
506
|
-
} else this.writeln("unknown");
|
|
507
|
-
}
|
|
508
|
-
this.popln();
|
|
509
|
-
}
|
|
510
|
-
renderPk(node) {
|
|
511
|
-
const isMongoCollection = !!node.annotations?.some((a) => a.name === "db.mongo.collection");
|
|
512
|
-
let struct;
|
|
513
|
-
if (node.hasExtends) struct = this.doc.resolveInterfaceExtends(node);
|
|
514
|
-
if (!struct) struct = node.getDefinition();
|
|
515
|
-
if (!struct || !isStructure(struct)) return;
|
|
516
|
-
const structNode = struct;
|
|
517
|
-
const pkProps = [];
|
|
518
|
-
const uniqueByIndex = new Map();
|
|
519
|
-
for (const [name, prop] of structNode.props) {
|
|
520
|
-
if (prop.token("identifier")?.pattern) continue;
|
|
521
|
-
if (isMongoCollection && name === "_id") continue;
|
|
522
|
-
if (prop.countAnnotations("meta.id") > 0) pkProps.push({
|
|
523
|
-
name,
|
|
524
|
-
prop
|
|
525
|
-
});
|
|
526
|
-
if (prop.annotations) {
|
|
527
|
-
for (const ann of prop.annotations) if (ann.name === "db.index.unique") {
|
|
528
|
-
const indexName = ann.args[0]?.text ?? name;
|
|
529
|
-
let group = uniqueByIndex.get(indexName);
|
|
530
|
-
if (!group) {
|
|
531
|
-
group = [];
|
|
532
|
-
uniqueByIndex.set(indexName, group);
|
|
533
|
-
}
|
|
534
|
-
group.push({
|
|
535
|
-
name,
|
|
536
|
-
prop
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
const uniqueProps = [];
|
|
542
|
-
const pkNames = new Set(pkProps.map((p) => p.name));
|
|
543
|
-
for (const fields of uniqueByIndex.values()) if (fields.length === 1 && !pkNames.has(fields[0].name)) uniqueProps.push(fields[0]);
|
|
544
|
-
if (pkProps.length === 0 && uniqueProps.length === 0 && !isMongoCollection) return;
|
|
545
|
-
let mongoIdType;
|
|
546
|
-
if (isMongoCollection) {
|
|
547
|
-
const idProp = structNode.props.get("_id");
|
|
548
|
-
if (idProp) mongoIdType = this.renderTypeDefString(idProp.getDefinition()).trim();
|
|
549
|
-
mongoIdType ?? (mongoIdType = "string");
|
|
550
|
-
}
|
|
551
|
-
const uniqueTypes = [];
|
|
552
|
-
const seenTypes = new Set();
|
|
553
|
-
for (const { prop } of uniqueProps) {
|
|
554
|
-
const rendered = this.renderTypeDefString(prop.getDefinition()).trim();
|
|
555
|
-
if (!seenTypes.has(rendered)) {
|
|
556
|
-
seenTypes.add(rendered);
|
|
557
|
-
uniqueTypes.push(rendered);
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
this.writeln();
|
|
561
|
-
const uniqueSuffix = uniqueTypes.length > 0 ? ` | ${uniqueTypes.join(" | ")}` : "";
|
|
562
|
-
if (pkProps.length === 0 && !isMongoCollection) this.writeln(`static __pk: ${uniqueTypes.join(" | ")}`);
|
|
563
|
-
else if (pkProps.length === 0) this.writeln(`static __pk: ${mongoIdType}${uniqueSuffix}`);
|
|
564
|
-
else if (pkProps.length === 1) {
|
|
565
|
-
this.write("static __pk: ");
|
|
566
|
-
if (isMongoCollection) this.write(`${mongoIdType} | `);
|
|
567
|
-
const renderedDef = this.renderTypeDefString(pkProps[0].prop.getDefinition()).trim();
|
|
568
|
-
this.writeln(`${renderedDef}${uniqueSuffix}`);
|
|
569
|
-
} else {
|
|
570
|
-
this.write("static __pk: ");
|
|
571
|
-
if (isMongoCollection) this.write(`${mongoIdType} | `);
|
|
572
|
-
this.blockln("{}");
|
|
573
|
-
for (const { name, prop } of pkProps) {
|
|
574
|
-
this.write(wrapProp(name), ": ");
|
|
575
|
-
const renderedDef = this.renderTypeDefString(prop.getDefinition());
|
|
576
|
-
renderedDef.split("\n").forEach((l) => this.writeln(l));
|
|
577
|
-
}
|
|
578
|
-
this.pop();
|
|
579
|
-
this.writeln(uniqueSuffix);
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
renderDimMeasure(node) {
|
|
583
|
-
let struct;
|
|
584
|
-
if (node.hasExtends) struct = this.doc.resolveInterfaceExtends(node);
|
|
585
|
-
if (!struct) struct = node.getDefinition();
|
|
586
|
-
if (!struct || !isStructure(struct)) return;
|
|
587
|
-
const structNode = struct;
|
|
588
|
-
const dims = [];
|
|
589
|
-
const measures = [];
|
|
590
|
-
for (const [name, prop] of structNode.props) {
|
|
591
|
-
if (prop.token("identifier")?.pattern) continue;
|
|
592
|
-
if (prop.countAnnotations("db.column.dimension") > 0) dims.push(name);
|
|
593
|
-
if (prop.countAnnotations("db.column.measure") > 0) measures.push(name);
|
|
594
|
-
}
|
|
595
|
-
if (dims.length === 0 && measures.length === 0) return;
|
|
596
|
-
this.writeln();
|
|
597
|
-
if (dims.length > 0) this.writeln(`static __dim: ${dims.map((d) => `"${escapeQuotes(d)}"`).join(" | ")}`);
|
|
598
|
-
if (measures.length > 0) this.writeln(`static __measure: ${measures.map((m) => `"${escapeQuotes(m)}"`).join(" | ")}`);
|
|
599
|
-
}
|
|
600
|
-
phantomPropType(def) {
|
|
601
|
-
if (!def) return undefined;
|
|
602
|
-
if (isPrimitive(def) && def.config.type === "phantom") return def.id;
|
|
603
|
-
if (isRef(def)) {
|
|
604
|
-
const ref = def;
|
|
605
|
-
const unwound = this.doc.unwindType(ref.id, ref.chain)?.def;
|
|
606
|
-
if (isPrimitive(unwound) && unwound.config.type === "phantom") return ref.hasChain ? `${ref.id}.${ref.chain.map((c) => c.text).join(".")}` : ref.id;
|
|
607
|
-
}
|
|
608
|
-
return undefined;
|
|
609
|
-
}
|
|
610
|
-
isTypeTarget(name, doc) {
|
|
611
|
-
const d = doc || this.doc;
|
|
612
|
-
const decl = d.getDeclarationOwnerNode(name);
|
|
613
|
-
if (!decl?.node) return false;
|
|
614
|
-
if (decl.node.entity === "type") return true;
|
|
615
|
-
if (decl.node.entity === "interface") return false;
|
|
616
|
-
if (decl.node.entity === "annotate") return this.isTypeTarget(decl.node.targetName, decl.doc);
|
|
617
|
-
return false;
|
|
618
|
-
}
|
|
619
|
-
renderJsDoc(node) {
|
|
620
|
-
const range = node.token("identifier")?.range;
|
|
621
|
-
const rangeStr = range ? `:${range.start.line + 1}:${range.start.character + 1}` : "";
|
|
622
|
-
this.writeln(`/**`);
|
|
623
|
-
this.writeln(` * Atscript ${node.entity} **${node.id}**`);
|
|
624
|
-
this.writeln(` * @see {@link ./${this.doc.name}${rangeStr}}`);
|
|
625
|
-
this.writeln(` */`);
|
|
626
|
-
}
|
|
627
|
-
constructor(doc, opts) {
|
|
628
|
-
super(doc), _define_property$1(this, "opts", void 0), this.opts = opts;
|
|
629
|
-
}
|
|
630
|
-
};
|
|
631
|
-
function renderPrimitiveTypeDef(def) {
|
|
632
|
-
if (!def) return "unknown";
|
|
633
|
-
if (typeof def === "string") return def === "void" ? "undefined" : def;
|
|
634
|
-
switch (def.kind) {
|
|
635
|
-
case "final": return def.value === "void" ? "undefined" : def.value;
|
|
636
|
-
case "union": return def.items.map(renderPrimitiveTypeDef).join(" | ");
|
|
637
|
-
case "intersection": return def.items.map(renderPrimitiveTypeDef).join(" & ");
|
|
638
|
-
case "tuple": return `[${def.items.map(renderPrimitiveTypeDef).join(", ")}]`;
|
|
639
|
-
case "array": return `${renderPrimitiveTypeDef(def.of)}[]`;
|
|
640
|
-
case "object": {
|
|
641
|
-
const props = Object.entries(def.props).map(([key, val]) => `${wrapProp(key)}${typeof val === "object" && val.optional ? "?" : ""}: ${renderPrimitiveTypeDef(val)}`).join("; ");
|
|
642
|
-
return `{ ${props} }`;
|
|
643
|
-
}
|
|
644
|
-
default: return "unknown";
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
//#endregion
|
|
649
|
-
//#region packages/typescript/src/codegen/js-renderer.ts
|
|
650
|
-
function _define_property(obj, key, value) {
|
|
651
|
-
if (key in obj) Object.defineProperty(obj, key, {
|
|
652
|
-
value,
|
|
653
|
-
enumerable: true,
|
|
654
|
-
configurable: true,
|
|
655
|
-
writable: true
|
|
656
|
-
});
|
|
657
|
-
else obj[key] = value;
|
|
658
|
-
return obj;
|
|
659
|
-
}
|
|
660
|
-
const QUERY_OP_MAP = {
|
|
661
|
-
"=": "$eq",
|
|
662
|
-
"!=": "$ne",
|
|
663
|
-
">": "$gt",
|
|
664
|
-
">=": "$gte",
|
|
665
|
-
"<": "$lt",
|
|
666
|
-
"<=": "$lte",
|
|
667
|
-
"in": "$in",
|
|
668
|
-
"not in": "$nin",
|
|
669
|
-
"matches": "$regex",
|
|
670
|
-
"exists": "$exists",
|
|
671
|
-
"not exists": "$exists"
|
|
672
|
-
};
|
|
673
|
-
var JsRenderer = class extends BaseRenderer {
|
|
674
|
-
pre() {
|
|
675
|
-
this.writeln("// prettier-ignore-start");
|
|
676
|
-
this.writeln("/* eslint-disable */");
|
|
677
|
-
this.writeln("/* oxlint-disable */");
|
|
678
|
-
let hasMutatingAnnotate = false;
|
|
679
|
-
const nodesByName = new Map();
|
|
680
|
-
for (const node of this.doc.nodes) {
|
|
681
|
-
if (node.entity === "annotate" && node.isMutating) hasMutatingAnnotate = true;
|
|
682
|
-
if (node.__typeId !== null && node.__typeId !== undefined && node.id) {
|
|
683
|
-
const name = node.id;
|
|
684
|
-
if (!nodesByName.has(name)) nodesByName.set(name, []);
|
|
685
|
-
nodesByName.get(name).push(node);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
for (const [name, nodes] of nodesByName) if (nodes.length === 1) this.typeIds.set(nodes[0], name);
|
|
689
|
-
else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}__${i + 1}`);
|
|
690
|
-
const imports = ["defineAnnotatedType as $", "annotate as $a"];
|
|
691
|
-
if (hasMutatingAnnotate) imports.push("cloneRefProp as $c");
|
|
692
|
-
const jsonSchemaMode = resolveJsonSchemaMode(this.opts);
|
|
693
|
-
if (jsonSchemaMode === "lazy") imports.push("buildJsonSchema as $$");
|
|
694
|
-
if (this.opts?.exampleData) imports.push("createDataFromAnnotatedType as $e");
|
|
695
|
-
if (jsonSchemaMode === false) imports.push("throwFeatureDisabled as $d");
|
|
696
|
-
this.writeln(`import { ${imports.join(", ")} } from "@atscript/typescript/utils"`);
|
|
697
|
-
}
|
|
698
|
-
buildAdHocMap(annotateNodes) {
|
|
699
|
-
const map = new Map();
|
|
700
|
-
for (const annotateNode of annotateNodes) for (const entry of annotateNode.entries) {
|
|
701
|
-
const path$1 = entry.hasChain ? [entry.id, ...entry.chain.map((c) => c.text)].join(".") : entry.id;
|
|
702
|
-
const anns = entry.annotations || [];
|
|
703
|
-
if (anns.length > 0) {
|
|
704
|
-
const existing = map.get(path$1);
|
|
705
|
-
if (existing) existing.push(...anns);
|
|
706
|
-
else map.set(path$1, [...anns]);
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
return map.size > 0 ? map : null;
|
|
710
|
-
}
|
|
711
|
-
/**
|
|
712
|
-
* Checks if any ad-hoc annotation path extends beyond the current _propPath,
|
|
713
|
-
* meaning annotations target properties inside a referenced type.
|
|
714
|
-
*/ hasAdHocAnnotationsThroughRef() {
|
|
715
|
-
if (!this._adHocAnnotations || this._propPath.length === 0) return false;
|
|
716
|
-
const prefix = `${this._propPath.join(".")}.`;
|
|
717
|
-
for (const key of this._adHocAnnotations.keys()) if (key.startsWith(prefix)) return true;
|
|
718
|
-
return false;
|
|
719
|
-
}
|
|
720
|
-
post() {
|
|
721
|
-
for (const node of this.postAnnotate) if (node.entity === "annotate") {
|
|
722
|
-
const annotateNode = node;
|
|
723
|
-
if (annotateNode.isMutating) this.renderMutatingAnnotateNode(annotateNode);
|
|
724
|
-
else {
|
|
725
|
-
const unwound = this.doc.unwindType(annotateNode.targetName);
|
|
726
|
-
if (unwound?.def) {
|
|
727
|
-
let def = this.doc.mergeIntersection(unwound.def);
|
|
728
|
-
if (isInterface(def)) if (def.hasExtends) {
|
|
729
|
-
const resolved = unwound.doc.resolveInterfaceExtends(def);
|
|
730
|
-
if (resolved) def = resolved;
|
|
731
|
-
else def = def.getDefinition() || def;
|
|
732
|
-
} else def = def.getDefinition() || def;
|
|
733
|
-
this._adHocAnnotations = this.buildAdHocMap([annotateNode]);
|
|
734
|
-
this.annotateType(def, node.id);
|
|
735
|
-
this._adHocAnnotations = null;
|
|
736
|
-
this.indent();
|
|
737
|
-
this.defineMetadataForAnnotateAlias(annotateNode);
|
|
738
|
-
this.unindent();
|
|
739
|
-
this.writeln();
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
} else {
|
|
743
|
-
let def = node.getDefinition();
|
|
744
|
-
if (isInterface(node) && node.hasExtends) {
|
|
745
|
-
const resolved = this.doc.resolveInterfaceExtends(node);
|
|
746
|
-
if (resolved) def = resolved;
|
|
747
|
-
}
|
|
748
|
-
this.annotateType(def, node.id);
|
|
749
|
-
this.indent().defineMetadata(node).unindent();
|
|
750
|
-
this.writeln();
|
|
751
|
-
}
|
|
752
|
-
this.writeln("// prettier-ignore-end");
|
|
753
|
-
super.post();
|
|
754
|
-
}
|
|
755
|
-
renderClassStatics(node) {
|
|
756
|
-
this.writeln("static __is_atscript_annotated_type = true");
|
|
757
|
-
this.writeln("static type = {}");
|
|
758
|
-
this.writeln("static metadata = new Map()");
|
|
759
|
-
const typeId = this.typeIds.get(node);
|
|
760
|
-
if (typeId) this.writeln(`static id = "${typeId}"`);
|
|
761
|
-
this.renderJsonSchemaMethod(node);
|
|
762
|
-
this.renderExampleDataMethod(node);
|
|
763
|
-
this.renderDimMeasure(node);
|
|
764
|
-
}
|
|
765
|
-
renderInterface(node) {
|
|
766
|
-
this.renderDefinitionClass(node);
|
|
767
|
-
}
|
|
768
|
-
renderType(node) {
|
|
769
|
-
this.renderDefinitionClass(node);
|
|
770
|
-
}
|
|
771
|
-
renderAnnotate(node) {
|
|
772
|
-
if (node.isMutating) {
|
|
773
|
-
this.postAnnotate.push(node);
|
|
774
|
-
return;
|
|
775
|
-
}
|
|
776
|
-
if (!this.doc.unwindType(node.targetName)?.def) return;
|
|
777
|
-
this.renderDefinitionClass(node);
|
|
778
|
-
}
|
|
779
|
-
renderDefinitionClass(node) {
|
|
780
|
-
this.writeln();
|
|
781
|
-
const exported = node.token("export")?.text === "export";
|
|
782
|
-
this.write(exported ? "export " : "");
|
|
783
|
-
this.write(`class ${node.id} `);
|
|
784
|
-
this.blockln("{}");
|
|
785
|
-
this.renderClassStatics(node);
|
|
786
|
-
this.popln();
|
|
787
|
-
this.postAnnotate.push(node);
|
|
788
|
-
this.writeln();
|
|
789
|
-
}
|
|
790
|
-
renderJsonSchemaMethod(node) {
|
|
791
|
-
const mode = resolveJsonSchemaMode(this.opts);
|
|
792
|
-
const hasAnnotation = node.countAnnotations("emit.jsonSchema") > 0;
|
|
793
|
-
if (hasAnnotation || mode === "bundle") {
|
|
794
|
-
const schema = JSON.stringify(buildJsonSchema(this.toAnnotatedType(node)));
|
|
795
|
-
this.writeln("static toJsonSchema() {");
|
|
796
|
-
this.indent().writeln(`return ${schema}`).unindent();
|
|
797
|
-
this.writeln("}");
|
|
798
|
-
} else if (mode === "lazy") {
|
|
799
|
-
this.writeln("static toJsonSchema() {");
|
|
800
|
-
this.indent().writeln("return this._jsonSchema ?? (this._jsonSchema = $$(this))").unindent();
|
|
801
|
-
this.writeln("}");
|
|
802
|
-
} else {
|
|
803
|
-
this.writeln("static toJsonSchema() {");
|
|
804
|
-
this.indent().writeln("$d(\"JSON Schema\", \"jsonSchema\", \"emit.jsonSchema\")").unindent();
|
|
805
|
-
this.writeln("}");
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
renderExampleDataMethod(_node) {
|
|
809
|
-
if (this.opts?.exampleData) {
|
|
810
|
-
this.writeln("static toExampleData() {");
|
|
811
|
-
this.indent().writeln("return $e(this, { mode: \"example\" })").unindent();
|
|
812
|
-
this.writeln("}");
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
renderDimMeasure(node) {
|
|
816
|
-
if (!isInterface(node)) return;
|
|
817
|
-
const interfaceNode = node;
|
|
818
|
-
if (!interfaceNode.annotations?.some((a) => a.name === "db.table" || a.name === "db.view" || a.name === "db.view.for")) return;
|
|
819
|
-
let struct;
|
|
820
|
-
if (interfaceNode.hasExtends) struct = this.doc.resolveInterfaceExtends(interfaceNode);
|
|
821
|
-
if (!struct) struct = interfaceNode.getDefinition();
|
|
822
|
-
if (!struct || !isStructure(struct)) return;
|
|
823
|
-
const structNode = struct;
|
|
824
|
-
const dims = [];
|
|
825
|
-
const measures = [];
|
|
826
|
-
for (const [name, prop] of structNode.props) {
|
|
827
|
-
if (prop.token("identifier")?.pattern) continue;
|
|
828
|
-
if (prop.countAnnotations("db.column.dimension") > 0) dims.push(name);
|
|
829
|
-
if (prop.countAnnotations("db.column.measure") > 0) measures.push(name);
|
|
830
|
-
}
|
|
831
|
-
if (dims.length > 0) this.writeln(`static dimensions = [${dims.map((d) => `'${escapeQuotes(d)}'`).join(", ")}]`);
|
|
832
|
-
if (measures.length > 0) this.writeln(`static measures = [${measures.map((m) => `'${escapeQuotes(m)}'`).join(", ")}]`);
|
|
833
|
-
}
|
|
834
|
-
toAnnotatedType(node) {
|
|
835
|
-
return this.toAnnotatedHandle(node).$type;
|
|
836
|
-
}
|
|
837
|
-
toAnnotatedHandle(node, skipAnnotations = false) {
|
|
838
|
-
if (!node) return defineAnnotatedType();
|
|
839
|
-
switch (node.entity) {
|
|
840
|
-
case "interface":
|
|
841
|
-
case "type": {
|
|
842
|
-
let def = node.getDefinition();
|
|
843
|
-
if (isInterface(node) && node.hasExtends) {
|
|
844
|
-
const resolved = this.doc.resolveInterfaceExtends(node);
|
|
845
|
-
if (resolved) def = resolved;
|
|
846
|
-
}
|
|
847
|
-
const handle = this.toAnnotatedHandle(def, true);
|
|
848
|
-
const typeId = this.typeIds.get(node) ?? (node.__typeId !== null && node.__typeId !== undefined ? node.id : undefined);
|
|
849
|
-
if (typeId) handle.id(typeId);
|
|
850
|
-
return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
851
|
-
}
|
|
852
|
-
case "prop": {
|
|
853
|
-
const prop = node;
|
|
854
|
-
const def = prop.getDefinition();
|
|
855
|
-
const handle = this.toAnnotatedHandle(def, true);
|
|
856
|
-
if (!skipAnnotations) {
|
|
857
|
-
this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(prop));
|
|
858
|
-
if (prop.token("optional")) handle.optional();
|
|
859
|
-
}
|
|
860
|
-
return handle;
|
|
861
|
-
}
|
|
862
|
-
case "ref": {
|
|
863
|
-
const ref = node;
|
|
864
|
-
const decl = this.doc.unwindType(ref.id, ref.chain)?.def;
|
|
865
|
-
const handle = this.toAnnotatedHandle(decl, true);
|
|
866
|
-
return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
867
|
-
}
|
|
868
|
-
case "primitive": {
|
|
869
|
-
const prim = node;
|
|
870
|
-
const handle = defineAnnotatedType();
|
|
871
|
-
handle.designType(prim.id === "never" ? "never" : prim.config.type);
|
|
872
|
-
if (!skipAnnotations) this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
873
|
-
return handle;
|
|
874
|
-
}
|
|
875
|
-
case "const": {
|
|
876
|
-
const c = node;
|
|
877
|
-
const handle = defineAnnotatedType();
|
|
878
|
-
const t = c.token("identifier")?.type;
|
|
879
|
-
handle.designType(t === "number" ? "number" : "string");
|
|
880
|
-
handle.value(t === "number" ? Number(c.id) : c.id);
|
|
881
|
-
return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
882
|
-
}
|
|
883
|
-
case "structure": {
|
|
884
|
-
const struct = node;
|
|
885
|
-
const handle = defineAnnotatedType("object");
|
|
886
|
-
for (const prop of Array.from(struct.props.values())) {
|
|
887
|
-
const propHandle = this.toAnnotatedHandle(prop);
|
|
888
|
-
const pattern = prop.token("identifier")?.pattern;
|
|
889
|
-
if (pattern) handle.propPattern(pattern, propHandle.$type);
|
|
890
|
-
else handle.prop(prop.id, propHandle.$type);
|
|
891
|
-
}
|
|
892
|
-
return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
893
|
-
}
|
|
894
|
-
case "group": {
|
|
895
|
-
const group = node;
|
|
896
|
-
const kind = group.op === "|" ? "union" : "intersection";
|
|
897
|
-
const handle = defineAnnotatedType(kind);
|
|
898
|
-
for (const item of group.unwrap()) handle.item(this.toAnnotatedHandle(item).$type);
|
|
899
|
-
return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
900
|
-
}
|
|
901
|
-
case "tuple": {
|
|
902
|
-
const group = node;
|
|
903
|
-
const handle = defineAnnotatedType("tuple");
|
|
904
|
-
for (const item of group.unwrap()) handle.item(this.toAnnotatedHandle(item).$type);
|
|
905
|
-
return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
906
|
-
}
|
|
907
|
-
case "array": {
|
|
908
|
-
const arr = node;
|
|
909
|
-
const handle = defineAnnotatedType("array");
|
|
910
|
-
handle.of(this.toAnnotatedHandle(arr.getDefinition()).$type);
|
|
911
|
-
return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
912
|
-
}
|
|
913
|
-
default: {
|
|
914
|
-
const handle = defineAnnotatedType();
|
|
915
|
-
return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
applyExpectAnnotations(handle, annotations) {
|
|
920
|
-
annotations?.forEach((a) => {
|
|
921
|
-
switch (a.name) {
|
|
922
|
-
case "expect.minLength":
|
|
923
|
-
case "expect.maxLength": {
|
|
924
|
-
if (a.args[0]) handle.annotate(a.name, {
|
|
925
|
-
length: Number(a.args[0].text),
|
|
926
|
-
message: a.args[1]?.text
|
|
927
|
-
});
|
|
928
|
-
break;
|
|
929
|
-
}
|
|
930
|
-
case "expect.min": {
|
|
931
|
-
if (a.args[0]) handle.annotate(a.name, {
|
|
932
|
-
minValue: Number(a.args[0].text),
|
|
933
|
-
message: a.args[1]?.text
|
|
934
|
-
});
|
|
935
|
-
break;
|
|
936
|
-
}
|
|
937
|
-
case "expect.max": {
|
|
938
|
-
if (a.args[0]) handle.annotate(a.name, {
|
|
939
|
-
maxValue: Number(a.args[0].text),
|
|
940
|
-
message: a.args[1]?.text
|
|
941
|
-
});
|
|
942
|
-
break;
|
|
943
|
-
}
|
|
944
|
-
case "expect.pattern": {
|
|
945
|
-
handle.annotate(a.name, {
|
|
946
|
-
pattern: a.args[0]?.text || "",
|
|
947
|
-
flags: a.args[1]?.text,
|
|
948
|
-
message: a.args[2]?.text
|
|
949
|
-
}, true);
|
|
950
|
-
break;
|
|
951
|
-
}
|
|
952
|
-
case "expect.int": {
|
|
953
|
-
handle.annotate(a.name, true);
|
|
954
|
-
break;
|
|
955
|
-
}
|
|
956
|
-
case "expect.array.uniqueItems":
|
|
957
|
-
case "expect.array.key": {
|
|
958
|
-
handle.annotate(a.name, { message: a.args[0]?.text });
|
|
959
|
-
break;
|
|
960
|
-
}
|
|
961
|
-
default:
|
|
962
|
-
}
|
|
963
|
-
});
|
|
964
|
-
return handle;
|
|
965
|
-
}
|
|
966
|
-
annotateType(_node, name) {
|
|
967
|
-
if (!_node) return this;
|
|
968
|
-
const node = this.doc.mergeIntersection(_node);
|
|
969
|
-
let kind = node.entity;
|
|
970
|
-
switch (node.entity) {
|
|
971
|
-
case "ref": {
|
|
972
|
-
const ref = node;
|
|
973
|
-
const decl = this.doc.unwindType(ref.id, ref.chain)?.def;
|
|
974
|
-
if (isPrimitive(decl)) {
|
|
975
|
-
const ownerDecl$1 = this.doc.getDeclarationOwnerNode(ref.id);
|
|
976
|
-
if (!ownerDecl$1?.node || ownerDecl$1.node.entity !== "type" && ownerDecl$1.node.entity !== "interface") {
|
|
977
|
-
this.annotateType(decl, name);
|
|
978
|
-
return this;
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
if (this._adHocAnnotations && this.hasAdHocAnnotationsThroughRef()) {
|
|
982
|
-
let resolved = decl ? this.doc.mergeIntersection(decl) : undefined;
|
|
983
|
-
if (resolved && isInterface(resolved)) resolved = resolved.getDefinition() || resolved;
|
|
984
|
-
if (resolved) {
|
|
985
|
-
this.annotateType(resolved, name);
|
|
986
|
-
return this;
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
const chain = ref.hasChain ? `, [${ref.chain.map((c) => `"${escapeQuotes(c.text)}"`).join(", ")}]` : "";
|
|
990
|
-
const ownerDecl = this.doc.getDeclarationOwnerNode(ref.id);
|
|
991
|
-
const isImported = ownerDecl ? ownerDecl.doc !== this.doc : false;
|
|
992
|
-
const refExpr = isImported ? `() => ${ref.id}` : ref.id;
|
|
993
|
-
this.writeln(`$(${name ? `"", ${name}` : ""})`).indent().writeln(`.refTo(${refExpr}${chain})`);
|
|
994
|
-
if (!ref.hasChain) {
|
|
995
|
-
if (ownerDecl?.node) {
|
|
996
|
-
const typeAnnotations = ownerDecl.doc.evalAnnotationsForNode(ownerDecl.node);
|
|
997
|
-
typeAnnotations?.forEach((an) => {
|
|
998
|
-
this.resolveAnnotationValue(ownerDecl.node, an);
|
|
999
|
-
});
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
this.unindent();
|
|
1003
|
-
return this;
|
|
1004
|
-
}
|
|
1005
|
-
case "primitive": {
|
|
1006
|
-
this.definePrimitive(node, name);
|
|
1007
|
-
return this;
|
|
1008
|
-
}
|
|
1009
|
-
case "const": {
|
|
1010
|
-
this.writeln(`$(${name ? `"", ${name}` : ""})`).indent().defineConst(node).unindent();
|
|
1011
|
-
return this;
|
|
1012
|
-
}
|
|
1013
|
-
case "structure": {
|
|
1014
|
-
this.writeln(`$("object"${name ? `, ${name}` : ""})`).indent().defineObject(node).unindent();
|
|
1015
|
-
return this;
|
|
1016
|
-
}
|
|
1017
|
-
case "group": {
|
|
1018
|
-
kind = node.op === "|" ? "union" : "intersection";
|
|
1019
|
-
this.writeln(`$("${kind}"${name ? `, ${name}` : ""})`).indent().defineGroup(node).unindent();
|
|
1020
|
-
return this;
|
|
1021
|
-
}
|
|
1022
|
-
case "tuple": {
|
|
1023
|
-
this.writeln(`$("tuple"${name ? `, ${name}` : ""})`).indent().defineGroup(node).unindent();
|
|
1024
|
-
return this;
|
|
1025
|
-
}
|
|
1026
|
-
case "array": {
|
|
1027
|
-
this.writeln(`$("array"${name ? `, ${name}` : ""})`).indent().defineArray(node).unindent();
|
|
1028
|
-
return this;
|
|
1029
|
-
}
|
|
1030
|
-
default: return this;
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
defineConst(node) {
|
|
1034
|
-
const t = node.token("identifier")?.type;
|
|
1035
|
-
const designType = t === "text" ? "string" : t === "number" ? "number" : "unknown";
|
|
1036
|
-
this.writeln(`.designType("${escapeQuotes(designType)}")`);
|
|
1037
|
-
this.writeln(`.value(${t === "text" ? `"${escapeQuotes(node.id)}"` : node.id})`);
|
|
1038
|
-
return this;
|
|
1039
|
-
}
|
|
1040
|
-
definePrimitive(node, name) {
|
|
1041
|
-
this.renderPrimitiveDef(node.id === "never" ? "never" : node.config.type, name);
|
|
1042
|
-
this.writeln(` .tags(${Array.from(node.tags).map((f) => `"${escapeQuotes(f)}"`).join(", ")})`);
|
|
1043
|
-
return this;
|
|
1044
|
-
}
|
|
1045
|
-
renderPrimitiveDef(def, name) {
|
|
1046
|
-
const d = (t) => [`"${t || ""}"`, name].filter(Boolean).join(", ").replace(/^""$/, "");
|
|
1047
|
-
if (!def) return this.writeln(`$(${d()}).designType("any")`);
|
|
1048
|
-
if (typeof def === "string") return this.writeln(`$(${d()}).designType("${def === "void" ? "undefined" : def}")`);
|
|
1049
|
-
switch (def.kind) {
|
|
1050
|
-
case "final": return this.writeln(`$(${d()}).designType("${def.value === "void" ? "undefined" : def.value}")`);
|
|
1051
|
-
case "union":
|
|
1052
|
-
case "intersection":
|
|
1053
|
-
case "tuple": {
|
|
1054
|
-
this.writeln(`$(${d(def.kind)})`);
|
|
1055
|
-
this.indent();
|
|
1056
|
-
for (const itemDef of def.items) {
|
|
1057
|
-
this.write(`.item(`);
|
|
1058
|
-
this.indent();
|
|
1059
|
-
this.renderPrimitiveDef(itemDef);
|
|
1060
|
-
this.writeln(".$type");
|
|
1061
|
-
this.unindent();
|
|
1062
|
-
this.write(`)`);
|
|
1063
|
-
}
|
|
1064
|
-
this.unindent();
|
|
1065
|
-
return;
|
|
1066
|
-
}
|
|
1067
|
-
case "array": {
|
|
1068
|
-
this.writeln(`$(${d("array")})`);
|
|
1069
|
-
this.indent();
|
|
1070
|
-
this.write(".of(");
|
|
1071
|
-
this.indent();
|
|
1072
|
-
this.renderPrimitiveDef(def.of);
|
|
1073
|
-
this.writeln(`.$type`);
|
|
1074
|
-
this.unindent();
|
|
1075
|
-
this.writeln(`)`);
|
|
1076
|
-
this.unindent();
|
|
1077
|
-
return;
|
|
1078
|
-
}
|
|
1079
|
-
case "object": {
|
|
1080
|
-
this.writeln(`$(${d("object")})`);
|
|
1081
|
-
this.indent();
|
|
1082
|
-
for (const [key, propDef] of Object.entries(def.props)) {
|
|
1083
|
-
const optional = typeof propDef === "object" && propDef.optional;
|
|
1084
|
-
this.writeln(`.prop(`);
|
|
1085
|
-
this.indent();
|
|
1086
|
-
this.writeln(`"${escapeQuotes(key)}",`);
|
|
1087
|
-
this.renderPrimitiveDef(propDef);
|
|
1088
|
-
if (optional) this.writeln(".optional()");
|
|
1089
|
-
this.writeln(".$type");
|
|
1090
|
-
this.unindent();
|
|
1091
|
-
this.write(`)`);
|
|
1092
|
-
}
|
|
1093
|
-
for (const [key, propDef] of Object.entries(def.propsPatterns)) {
|
|
1094
|
-
const optional = typeof propDef === "object" && propDef.optional;
|
|
1095
|
-
this.writeln(`.propPattern(`);
|
|
1096
|
-
this.indent();
|
|
1097
|
-
this.writeln(`${key},`);
|
|
1098
|
-
this.renderPrimitiveDef(propDef);
|
|
1099
|
-
if (optional) this.writeln(".optional()");
|
|
1100
|
-
this.writeln(".$type");
|
|
1101
|
-
this.unindent();
|
|
1102
|
-
this.write(`)`);
|
|
1103
|
-
}
|
|
1104
|
-
this.unindent();
|
|
1105
|
-
return;
|
|
1106
|
-
}
|
|
1107
|
-
default: return this.writeln(`$(${d()}).designType("any")`);
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
defineObject(node) {
|
|
1111
|
-
const props = Array.from(node.props.values());
|
|
1112
|
-
for (const prop of props) {
|
|
1113
|
-
const pattern = prop.token("identifier")?.pattern;
|
|
1114
|
-
const optional = !!prop.token("optional");
|
|
1115
|
-
this._propPath.push(prop.id);
|
|
1116
|
-
if (pattern) {
|
|
1117
|
-
this.writeln(`.propPattern(`);
|
|
1118
|
-
this.indent();
|
|
1119
|
-
this.writeln(`/${pattern.source}/${pattern.flags},`);
|
|
1120
|
-
} else {
|
|
1121
|
-
this.writeln(`.prop(`);
|
|
1122
|
-
this.indent();
|
|
1123
|
-
this.writeln(`"${escapeQuotes(prop.id)}",`);
|
|
1124
|
-
}
|
|
1125
|
-
this.annotateType(prop.getDefinition());
|
|
1126
|
-
this.indent().defineMetadata(prop).unindent();
|
|
1127
|
-
if (optional) this.writeln(" .optional()");
|
|
1128
|
-
this.writeln(" .$type");
|
|
1129
|
-
this.unindent();
|
|
1130
|
-
this.write(`)`);
|
|
1131
|
-
this._propPath.pop();
|
|
1132
|
-
}
|
|
1133
|
-
this.writeln();
|
|
1134
|
-
return this;
|
|
1135
|
-
}
|
|
1136
|
-
defineGroup(node) {
|
|
1137
|
-
const items = node.unwrap();
|
|
1138
|
-
for (const item of items) this.write(".item(").indent().annotateType(item).write(" .$type").writeln(`)`).unindent();
|
|
1139
|
-
return this;
|
|
1140
|
-
}
|
|
1141
|
-
defineArray(node) {
|
|
1142
|
-
this.write(".of(").indent().annotateType(node.getDefinition()).write(" .$type").writeln(`)`).unindent();
|
|
1143
|
-
return this;
|
|
1144
|
-
}
|
|
1145
|
-
defineMetadata(node) {
|
|
1146
|
-
let annotations;
|
|
1147
|
-
const nodeDef = node.getDefinition?.();
|
|
1148
|
-
if (nodeDef && isRef(nodeDef)) {
|
|
1149
|
-
const refNode = nodeDef;
|
|
1150
|
-
if (!refNode.hasChain) {
|
|
1151
|
-
const resolved = this.doc.unwindType(refNode.id, refNode.chain)?.def;
|
|
1152
|
-
if (resolved && !isPrimitive(resolved)) annotations = node.annotations ?? [];
|
|
1153
|
-
else if (resolved && isPrimitive(resolved)) {
|
|
1154
|
-
const ownerDecl = this.doc.getDeclarationOwnerNode(refNode.id);
|
|
1155
|
-
if (ownerDecl?.node && (ownerDecl.node.entity === "type" || ownerDecl.node.entity === "interface")) annotations = node.annotations ?? [];
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
if (annotations === undefined) annotations = this.doc.evalAnnotationsForNode(node);
|
|
1160
|
-
if (this._adHocAnnotations && this._propPath.length > 0) {
|
|
1161
|
-
const path$1 = this._propPath.join(".");
|
|
1162
|
-
const adHoc = this._adHocAnnotations.get(path$1);
|
|
1163
|
-
if (adHoc) annotations = this.doc.mergeNodesAnnotations(annotations, adHoc);
|
|
1164
|
-
}
|
|
1165
|
-
annotations?.forEach((an) => {
|
|
1166
|
-
this.resolveAnnotationValue(node, an);
|
|
1167
|
-
});
|
|
1168
|
-
return this;
|
|
1169
|
-
}
|
|
1170
|
-
/**
|
|
1171
|
-
* For non-mutating annotate aliases: merge the target's type-level annotations
|
|
1172
|
-
* with the annotate block's own annotations (annotate's take priority).
|
|
1173
|
-
*/ defineMetadataForAnnotateAlias(annotateNode) {
|
|
1174
|
-
const annotateAnnotations = this.doc.evalAnnotationsForNode(annotateNode);
|
|
1175
|
-
const targetDecl = this.doc.getDeclarationOwnerNode(annotateNode.targetName);
|
|
1176
|
-
const targetAnnotations = targetDecl?.node ? targetDecl.doc.evalAnnotationsForNode(targetDecl.node) : undefined;
|
|
1177
|
-
const merged = this.doc.mergeNodesAnnotations(targetAnnotations, annotateAnnotations);
|
|
1178
|
-
merged.forEach((an) => {
|
|
1179
|
-
this.resolveAnnotationValue(annotateNode, an);
|
|
1180
|
-
});
|
|
1181
|
-
return this;
|
|
1182
|
-
}
|
|
1183
|
-
resolveAnnotationValue(node, an) {
|
|
1184
|
-
const { value, multiple } = this.computeAnnotationValue(node, an);
|
|
1185
|
-
if (multiple) this.writeln(`.annotate("${escapeQuotes(an.name)}", ${value}, true)`);
|
|
1186
|
-
else this.writeln(`.annotate("${escapeQuotes(an.name)}", ${value})`);
|
|
1187
|
-
}
|
|
1188
|
-
emitRefValue(text) {
|
|
1189
|
-
const dotIdx = text.indexOf(".");
|
|
1190
|
-
if (dotIdx === -1) return `() => ${text}`;
|
|
1191
|
-
const typeName = text.slice(0, dotIdx);
|
|
1192
|
-
const field = text.slice(dotIdx + 1);
|
|
1193
|
-
return `{ type: () => ${typeName}, field: "${escapeQuotes(field)}" }`;
|
|
1194
|
-
}
|
|
1195
|
-
emitArgValue(aSpec, argToken) {
|
|
1196
|
-
if (aSpec.type === "ref") return this.emitRefValue(argToken.text);
|
|
1197
|
-
if (aSpec.type === "query" && argToken.queryNode) return this.emitQueryTree(argToken.queryNode);
|
|
1198
|
-
return aSpec.type === "string" ? `"${escapeQuotes(argToken.text)}"` : argToken.text;
|
|
1199
|
-
}
|
|
1200
|
-
emitQueryTree(queryNode) {
|
|
1201
|
-
return this.emitQueryExpr(queryNode.expression);
|
|
1202
|
-
}
|
|
1203
|
-
emitQueryExpr(node) {
|
|
1204
|
-
if (isQueryLogical(node)) return this.emitQueryLogical(node);
|
|
1205
|
-
return this.emitQueryComparison(node);
|
|
1206
|
-
}
|
|
1207
|
-
emitQueryLogical(node) {
|
|
1208
|
-
if (node.operator === "not") return `{ "$not": ${this.emitQueryExpr(node.operands[0])} }`;
|
|
1209
|
-
const key = node.operator === "and" ? "$and" : "$or";
|
|
1210
|
-
const items = node.operands.map((op) => this.emitQueryExpr(op)).join(", ");
|
|
1211
|
-
return `{ "${key}": [${items}] }`;
|
|
1212
|
-
}
|
|
1213
|
-
emitQueryComparison(node) {
|
|
1214
|
-
const left = this.emitQueryFieldRef(node.left);
|
|
1215
|
-
const mappedOp = QUERY_OP_MAP[node.operator] || node.operator;
|
|
1216
|
-
const parts = [`left: ${left}`, `op: "${mappedOp}"`];
|
|
1217
|
-
if (node.right) {
|
|
1218
|
-
if ("fieldRef" in node.right && node.right.fieldRef) parts.push(`right: ${this.emitQueryFieldRef(node.right)}`);
|
|
1219
|
-
else if ("values" in node.right && node.right.values) {
|
|
1220
|
-
const values = node.right.values.map((v) => this.emitQueryLiteral(v)).join(", ");
|
|
1221
|
-
parts.push(`right: [${values}]`);
|
|
1222
|
-
} else if ("valueToken" in node.right) parts.push(`right: ${this.emitQueryLiteral(node.right)}`);
|
|
1223
|
-
} else if (node.operator === "exists") parts.push("right: true");
|
|
1224
|
-
else if (node.operator === "not exists") parts.push("right: false");
|
|
1225
|
-
return `{ ${parts.join(", ")} }`;
|
|
1226
|
-
}
|
|
1227
|
-
emitQueryFieldRef(node) {
|
|
1228
|
-
const parts = [];
|
|
1229
|
-
if (node.typeRef) parts.push(`type: () => ${node.typeRef.text}`);
|
|
1230
|
-
parts.push(`field: "${escapeQuotes(node.fieldRef.text)}"`);
|
|
1231
|
-
return `{ ${parts.join(", ")} }`;
|
|
1232
|
-
}
|
|
1233
|
-
emitQueryLiteral(node) {
|
|
1234
|
-
const token = node.valueToken;
|
|
1235
|
-
switch (token.type) {
|
|
1236
|
-
case "text": return `"${escapeQuotes(token.text)}"`;
|
|
1237
|
-
case "number": return token.text;
|
|
1238
|
-
case "regexp": {
|
|
1239
|
-
const match = /^\/(.*)\/[a-z]*$/.exec(token.text);
|
|
1240
|
-
return `"${escapeQuotes(match ? match[1] : token.text)}"`;
|
|
1241
|
-
}
|
|
1242
|
-
case "identifier": {
|
|
1243
|
-
if (token.text === "true" || token.text === "false") return token.text;
|
|
1244
|
-
if (token.text === "null" || token.text === "undefined") return "null";
|
|
1245
|
-
return token.text;
|
|
1246
|
-
}
|
|
1247
|
-
default: return token.text;
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
computeAnnotationValue(node, an) {
|
|
1251
|
-
const spec = this.doc.resolveAnnotation(an.name);
|
|
1252
|
-
let targetValue = "true";
|
|
1253
|
-
let multiple = false;
|
|
1254
|
-
if (spec) {
|
|
1255
|
-
multiple = spec.config.multiple;
|
|
1256
|
-
const length = spec.arguments.length;
|
|
1257
|
-
if (length !== 0) if (Array.isArray(spec.config.argument)) {
|
|
1258
|
-
targetValue = "{ ";
|
|
1259
|
-
let i = 0;
|
|
1260
|
-
for (const aSpec of spec.arguments) {
|
|
1261
|
-
if (an.args[i]) targetValue += `${wrapProp(aSpec.name)}: ${this.emitArgValue(aSpec, an.args[i])}${i === length - 1 ? "" : ", "} `;
|
|
1262
|
-
i++;
|
|
1263
|
-
}
|
|
1264
|
-
targetValue += "}";
|
|
1265
|
-
} else {
|
|
1266
|
-
const aSpec = spec.arguments[0];
|
|
1267
|
-
if (an.args[0]) targetValue = this.emitArgValue(aSpec, an.args[0]);
|
|
1268
|
-
else targetValue = "true";
|
|
1269
|
-
}
|
|
1270
|
-
} else {
|
|
1271
|
-
multiple = node.countAnnotations(an.name) > 1 || an.args.length > 1;
|
|
1272
|
-
if (an.args.length > 0) targetValue = an.args[0].type === "text" ? `"${escapeQuotes(an.args[0].text)}"` : an.args[0].text;
|
|
1273
|
-
}
|
|
1274
|
-
return {
|
|
1275
|
-
value: targetValue,
|
|
1276
|
-
multiple: !!multiple
|
|
1277
|
-
};
|
|
1278
|
-
}
|
|
1279
|
-
renderMutatingAnnotateNode(node) {
|
|
1280
|
-
const targetName = node.targetName;
|
|
1281
|
-
const targetDef = this.resolveTargetDef(targetName);
|
|
1282
|
-
this.writeln("// Ad-hoc annotations for ", targetName);
|
|
1283
|
-
const allClones = [];
|
|
1284
|
-
const entryAccessors = [];
|
|
1285
|
-
for (const entry of node.entries) {
|
|
1286
|
-
const anns = entry.annotations;
|
|
1287
|
-
if (!anns || anns.length === 0) continue;
|
|
1288
|
-
const parts = entry.hasChain ? [entry.id, ...entry.chain.map((c) => c.text)] : [entry.id];
|
|
1289
|
-
const { accessors, clones } = this.buildMutatingAccessors(targetName, targetDef, parts);
|
|
1290
|
-
allClones.push(...clones);
|
|
1291
|
-
entryAccessors.push({
|
|
1292
|
-
entry,
|
|
1293
|
-
accessors
|
|
1294
|
-
});
|
|
1295
|
-
}
|
|
1296
|
-
const cloneKeys = new Set();
|
|
1297
|
-
for (const clone of allClones) {
|
|
1298
|
-
const key = `${clone.parentPath}|${clone.propName}`;
|
|
1299
|
-
if (!cloneKeys.has(key)) {
|
|
1300
|
-
cloneKeys.add(key);
|
|
1301
|
-
this.writeln(`$c(${clone.parentPath}, "${escapeQuotes(clone.propName)}")`);
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
for (const { entry, accessors } of entryAccessors) for (const accessor of accessors) this.emitMutatingAnnotations(entry, entry.annotations, accessor);
|
|
1305
|
-
const topAnnotations = node.annotations;
|
|
1306
|
-
if (topAnnotations && topAnnotations.length > 0) this.emitMutatingAnnotations(node, topAnnotations, targetName);
|
|
1307
|
-
this.writeln();
|
|
1308
|
-
}
|
|
1309
|
-
emitMutatingAnnotations(node, annotations, accessor) {
|
|
1310
|
-
const cleared = new Set();
|
|
1311
|
-
for (const an of annotations) {
|
|
1312
|
-
const { value, multiple } = this.computeAnnotationValue(node, an);
|
|
1313
|
-
if (multiple) {
|
|
1314
|
-
if (!cleared.has(an.name)) {
|
|
1315
|
-
const spec = this.doc.resolveAnnotation(an.name);
|
|
1316
|
-
if (!spec || spec.config.mergeStrategy !== "append") this.writeln(`${accessor}.metadata.delete("${escapeQuotes(an.name)}")`);
|
|
1317
|
-
cleared.add(an.name);
|
|
1318
|
-
}
|
|
1319
|
-
this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value}, true)`);
|
|
1320
|
-
} else this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value})`);
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
resolveTargetDef(targetName) {
|
|
1324
|
-
const unwound = this.doc.unwindType(targetName);
|
|
1325
|
-
if (!unwound?.def) return undefined;
|
|
1326
|
-
let def = unwound.def;
|
|
1327
|
-
if (isInterface(def)) def = def.getDefinition() || def;
|
|
1328
|
-
return def;
|
|
1329
|
-
}
|
|
1330
|
-
/**
|
|
1331
|
-
* Builds the runtime accessor paths for mutating annotate entries.
|
|
1332
|
-
* Computes exact paths at compile time by walking the AST,
|
|
1333
|
-
* so the generated JS accesses props directly without runtime search.
|
|
1334
|
-
* Returns multiple paths when a property appears in multiple union branches.
|
|
1335
|
-
*/ buildMutatingAccessors(targetName, targetDef, parts) {
|
|
1336
|
-
let accessors = [{
|
|
1337
|
-
prefix: `${targetName}.type`,
|
|
1338
|
-
def: targetDef
|
|
1339
|
-
}];
|
|
1340
|
-
const clones = [];
|
|
1341
|
-
for (let i = 0; i < parts.length; i++) {
|
|
1342
|
-
const nextAccessors = [];
|
|
1343
|
-
for (const { prefix, def } of accessors) {
|
|
1344
|
-
const results = this.buildPropPaths(def, parts[i]);
|
|
1345
|
-
if (results.length > 0) for (const result of results) if (i < parts.length - 1) {
|
|
1346
|
-
if (result.propDef && isRef(result.propDef)) clones.push({
|
|
1347
|
-
parentPath: prefix,
|
|
1348
|
-
propName: parts[i]
|
|
1349
|
-
});
|
|
1350
|
-
nextAccessors.push({
|
|
1351
|
-
prefix: `${prefix}${result.path}?.type`,
|
|
1352
|
-
def: result.propDef
|
|
1353
|
-
});
|
|
1354
|
-
} else nextAccessors.push({
|
|
1355
|
-
prefix: `${prefix}${result.path}?`,
|
|
1356
|
-
def: result.propDef
|
|
1357
|
-
});
|
|
1358
|
-
else {
|
|
1359
|
-
const suffix = `.props.get("${escapeQuotes(parts[i])}")${i < parts.length - 1 ? "?.type" : "?"}`;
|
|
1360
|
-
nextAccessors.push({
|
|
1361
|
-
prefix: `${prefix}${suffix}`,
|
|
1362
|
-
def: undefined
|
|
1363
|
-
});
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1366
|
-
accessors = nextAccessors;
|
|
1367
|
-
}
|
|
1368
|
-
return {
|
|
1369
|
-
accessors: accessors.map((a) => a.prefix),
|
|
1370
|
-
clones
|
|
1371
|
-
};
|
|
1372
|
-
}
|
|
1373
|
-
/**
|
|
1374
|
-
* Finds a property in a type tree at compile time, returning all
|
|
1375
|
-
* matching runtime path strings and prop definitions for further chaining.
|
|
1376
|
-
* Returns multiple results when the same property appears in different union branches.
|
|
1377
|
-
*/ buildPropPaths(def, propName) {
|
|
1378
|
-
if (!def) return [];
|
|
1379
|
-
def = this.doc.mergeIntersection(def);
|
|
1380
|
-
if (isRef(def)) {
|
|
1381
|
-
const ref = def;
|
|
1382
|
-
const unwound = this.doc.unwindType(ref.id, ref.chain)?.def;
|
|
1383
|
-
return this.buildPropPaths(unwound, propName);
|
|
1384
|
-
}
|
|
1385
|
-
if (isInterface(def)) return this.buildPropPaths(def.getDefinition(), propName);
|
|
1386
|
-
if (isStructure(def)) {
|
|
1387
|
-
const prop = def.props.get(propName);
|
|
1388
|
-
if (prop) return [{
|
|
1389
|
-
path: `.props.get("${escapeQuotes(propName)}")`,
|
|
1390
|
-
propDef: prop.getDefinition()
|
|
1391
|
-
}];
|
|
1392
|
-
return [];
|
|
1393
|
-
}
|
|
1394
|
-
if (isGroup(def)) {
|
|
1395
|
-
const group = def;
|
|
1396
|
-
const items = group.unwrap();
|
|
1397
|
-
const results = [];
|
|
1398
|
-
for (let i = 0; i < items.length; i++) for (const result of this.buildPropPaths(items[i], propName)) results.push({
|
|
1399
|
-
path: `.items[${i}].type${result.path}`,
|
|
1400
|
-
propDef: result.propDef
|
|
1401
|
-
});
|
|
1402
|
-
return results;
|
|
1403
|
-
}
|
|
1404
|
-
return [];
|
|
1405
|
-
}
|
|
1406
|
-
constructor(doc, opts) {
|
|
1407
|
-
super(doc), _define_property(this, "opts", void 0), _define_property(this, "postAnnotate", void 0), _define_property(this, "_adHocAnnotations", void 0), _define_property(this, "_propPath", void 0), _define_property(this, "typeIds", void 0), this.opts = opts, this.postAnnotate = [], this._adHocAnnotations = null, this._propPath = [], this.typeIds = new Map();
|
|
1408
|
-
}
|
|
1409
|
-
};
|
|
1410
|
-
|
|
1411
|
-
//#endregion
|
|
1412
|
-
//#region packages/typescript/src/plugin.ts
|
|
1413
|
-
function resolveJsonSchemaMode(opts) {
|
|
1414
|
-
if (opts?.jsonSchema !== undefined) return opts.jsonSchema;
|
|
1415
|
-
return false;
|
|
1416
|
-
}
|
|
1417
|
-
const tsPlugin = (opts) => {
|
|
1418
|
-
return {
|
|
1419
|
-
name: "typescript",
|
|
1420
|
-
render(doc, format) {
|
|
1421
|
-
if (format === "dts" || format === DEFAULT_FORMAT) return [{
|
|
1422
|
-
fileName: `${doc.name}.d.ts`,
|
|
1423
|
-
content: new TypeRenderer(doc, opts).render()
|
|
1424
|
-
}];
|
|
1425
|
-
if (format === "js") return [{
|
|
1426
|
-
fileName: `${doc.name}.js`,
|
|
1427
|
-
content: new JsRenderer(doc, opts).render()
|
|
1428
|
-
}];
|
|
1429
|
-
},
|
|
1430
|
-
async buildEnd(output, format, repo) {
|
|
1431
|
-
if (format === "dts" || format === DEFAULT_FORMAT) {
|
|
1432
|
-
const annotations = await repo.getUsedAnnotations();
|
|
1433
|
-
const tags = await repo.getPrimitivesTags() || new Set();
|
|
1434
|
-
const rendered = [];
|
|
1435
|
-
for (const [key, val] of Object.entries(annotations)) {
|
|
1436
|
-
const multiple = val.multiple;
|
|
1437
|
-
const typeLine = Array.from(val.types).map((t) => {
|
|
1438
|
-
if (t.type === "object" && "props" in t) return `{ ${Object.entries(t.props).map(([k, v]) => `${wrapProp(k)}${v.optional ? "?" : ""}: ${v.type}`).join(", ")} }`;
|
|
1439
|
-
else return "optional" in t && t.optional ? `${t.type} | true` : t.type;
|
|
1440
|
-
}).join(" | ");
|
|
1441
|
-
rendered.push(`${wrapProp(key)}: ${multiple ? "(" : ""}${typeLine}${multiple ? ")[]" : ""}`);
|
|
1442
|
-
}
|
|
1443
|
-
const renderedTags = Array.from(tags).map((f) => `"${escapeQuotes(f)}"`).join(" | ");
|
|
1444
|
-
output.push({
|
|
1445
|
-
content: "// prettier-ignore-start\n/* eslint-disable */\n/* oxlint-disable */\n/**\n * 🪄 This file was generated by Atscript\n * It is generated based on annotations used in this project\n * Do not edit this file!\n *\n * Use `npx asc -f dts` command to re-generate this file\n */\nexport {}\n\ndeclare global {\n interface AtscriptMetadata {\n " + rendered.join("\n ") + "\n }\n" + " type AtscriptPrimitiveTags = " + renderedTags + "\n" + "}\n" + "// prettier-ignore-end",
|
|
1446
|
-
fileName: "atscript.d.ts",
|
|
1447
|
-
source: "",
|
|
1448
|
-
target: path.join(repo.root, "atscript.d.ts")
|
|
1449
|
-
});
|
|
1450
|
-
}
|
|
1451
|
-
}
|
|
1452
|
-
};
|
|
1453
|
-
};
|
|
1454
|
-
|
|
1455
|
-
//#endregion
|
|
1456
4
|
export { tsPlugin as default, tsPlugin };
|