@atscript/typescript 0.1.31 → 0.1.33
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/dist/cli.cjs +301 -236
- package/dist/index.cjs +301 -236
- package/dist/index.mjs +301 -236
- package/dist/utils.cjs +160 -143
- package/dist/utils.d.ts +28 -8
- package/dist/utils.mjs +160 -144
- package/package.json +2 -2
- package/skills/atscript-typescript/annotations.md +2 -1
package/dist/index.cjs
CHANGED
|
@@ -141,6 +141,9 @@ else obj[key] = value;
|
|
|
141
141
|
return obj;
|
|
142
142
|
}
|
|
143
143
|
var BaseRenderer = class extends CodePrinter {
|
|
144
|
+
get unused() {
|
|
145
|
+
return this._unused ?? (this._unused = new Set(this.doc.getUnusedTokens().map((t) => t.text)));
|
|
146
|
+
}
|
|
144
147
|
pre() {}
|
|
145
148
|
post() {}
|
|
146
149
|
render() {
|
|
@@ -194,15 +197,14 @@ var BaseRenderer = class extends CodePrinter {
|
|
|
194
197
|
}
|
|
195
198
|
}
|
|
196
199
|
constructor(doc) {
|
|
197
|
-
super(), _define_property$3(this, "doc", void 0), _define_property$3(this, "
|
|
198
|
-
this.unused = new Set(this.doc.getUnusedTokens().map((t) => t.text));
|
|
200
|
+
super(), _define_property$3(this, "doc", void 0), _define_property$3(this, "_unused", void 0), this.doc = doc;
|
|
199
201
|
}
|
|
200
202
|
};
|
|
201
203
|
|
|
202
204
|
//#endregion
|
|
203
205
|
//#region packages/typescript/src/codegen/utils.ts
|
|
206
|
+
const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
204
207
|
function wrapProp(name) {
|
|
205
|
-
const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
206
208
|
if (!validIdentifier.test(name)) return `"${escapeQuotes(name)}"`;
|
|
207
209
|
return name;
|
|
208
210
|
}
|
|
@@ -289,13 +291,14 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
289
291
|
}
|
|
290
292
|
if ((0, __atscript_core.isPrimitive)(def)) return this.write(renderPrimitiveTypeDef(def.config.type));
|
|
291
293
|
}
|
|
292
|
-
renderStructure(struct, asClass, interfaceNode) {
|
|
294
|
+
renderStructure(struct, asClass, interfaceNode, filterProps) {
|
|
293
295
|
this.blockln("{}");
|
|
294
296
|
const patterns = [];
|
|
295
297
|
const propsDefs = new Set();
|
|
296
|
-
for (const prop of
|
|
298
|
+
for (const prop of struct.props.values()) {
|
|
299
|
+
if (filterProps?.has(prop.id)) continue;
|
|
297
300
|
if (prop.token("identifier")?.pattern) {
|
|
298
|
-
patterns.push(prop);
|
|
301
|
+
if (!filterProps) patterns.push(prop);
|
|
299
302
|
continue;
|
|
300
303
|
}
|
|
301
304
|
const phantomType = this.phantomPropType(prop.getDefinition());
|
|
@@ -311,34 +314,36 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
311
314
|
}
|
|
312
315
|
if (patterns.length > 0) {
|
|
313
316
|
this.write(`[key: string]: `);
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
for (const def of defs) {
|
|
320
|
-
this.writeln();
|
|
321
|
-
this.write("| ");
|
|
322
|
-
def.split("\n").forEach((l) => this.write(l.trim()));
|
|
323
|
-
}
|
|
324
|
-
this.unindent();
|
|
317
|
+
for (const prop of patterns) propsDefs.add(this.renderTypeDefString(prop.getDefinition()));
|
|
318
|
+
const defs = Array.from(propsDefs);
|
|
319
|
+
if (defs.length > 1) {
|
|
320
|
+
this.indent();
|
|
321
|
+
for (const def of defs) {
|
|
325
322
|
this.writeln();
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
this.writeln(
|
|
332
|
-
this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
|
|
333
|
-
this.writeln(`static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${asClass}>`);
|
|
334
|
-
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. */");
|
|
335
|
-
this.writeln("static toJsonSchema: () => any");
|
|
336
|
-
if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
|
|
337
|
-
this.writeln("static toExampleData?: () => any");
|
|
338
|
-
if (interfaceNode && this.hasDbTable(interfaceNode)) this.renderFlat(interfaceNode);
|
|
323
|
+
this.write("| ");
|
|
324
|
+
def.split("\n").forEach((l) => this.write(l.trim()));
|
|
325
|
+
}
|
|
326
|
+
this.unindent();
|
|
327
|
+
this.writeln();
|
|
328
|
+
} else defs[0].split("\n").forEach((l) => this.writeln(l));
|
|
339
329
|
}
|
|
330
|
+
if (asClass) this.renderStaticDeclarations(asClass, interfaceNode);
|
|
340
331
|
this.pop();
|
|
341
332
|
}
|
|
333
|
+
renderStaticDeclarations(asClass, interfaceNode) {
|
|
334
|
+
this.writeln("static __is_atscript_annotated_type: true");
|
|
335
|
+
this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}, ${asClass}>`);
|
|
336
|
+
this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
|
|
337
|
+
this.writeln(`static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${asClass}>`);
|
|
338
|
+
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. */");
|
|
339
|
+
this.writeln("static toJsonSchema: () => any");
|
|
340
|
+
if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
|
|
341
|
+
this.writeln("static toExampleData?: () => any");
|
|
342
|
+
if (interfaceNode && this.hasDbTable(interfaceNode)) {
|
|
343
|
+
this.renderFlat(interfaceNode);
|
|
344
|
+
this.renderPk(interfaceNode);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
342
347
|
renderInterface(node) {
|
|
343
348
|
this.writeln();
|
|
344
349
|
const exported = node.token("export")?.text === "export";
|
|
@@ -362,7 +367,7 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
362
367
|
}
|
|
363
368
|
if (!firstParentProps && (0, __atscript_core.isStructure)(fpDef)) firstParentProps = fpDef.props;
|
|
364
369
|
}
|
|
365
|
-
this.
|
|
370
|
+
this.renderStructure(resolved, node.id, node, firstParentProps);
|
|
366
371
|
} else this.writeln("{}");
|
|
367
372
|
} else {
|
|
368
373
|
this.write(`class ${node.id} `);
|
|
@@ -372,35 +377,6 @@ else this.writeln("{}");
|
|
|
372
377
|
}
|
|
373
378
|
this.writeln();
|
|
374
379
|
}
|
|
375
|
-
/**
|
|
376
|
-
* Renders a structure block, optionally filtering out props that exist in a parent.
|
|
377
|
-
*/ renderStructureFiltered(struct, asClass, filterProps, interfaceNode) {
|
|
378
|
-
if (!filterProps) return this.renderStructure(struct, asClass, interfaceNode);
|
|
379
|
-
this.blockln("{}");
|
|
380
|
-
for (const prop of Array.from(struct.props.values())) {
|
|
381
|
-
if (filterProps.has(prop.id)) continue;
|
|
382
|
-
if (prop.token("identifier")?.pattern) continue;
|
|
383
|
-
const phantomType = this.phantomPropType(prop.getDefinition());
|
|
384
|
-
if (phantomType) {
|
|
385
|
-
this.writeln(`// ${prop.id}: ${phantomType}`);
|
|
386
|
-
continue;
|
|
387
|
-
}
|
|
388
|
-
const optional = !!prop.token("optional");
|
|
389
|
-
this.write(wrapProp(prop.id), optional ? "?" : "", ": ");
|
|
390
|
-
const renderedDef = this.renderTypeDefString(prop.getDefinition());
|
|
391
|
-
renderedDef.split("\n").forEach((l) => this.writeln(l));
|
|
392
|
-
}
|
|
393
|
-
this.writeln("static __is_atscript_annotated_type: true");
|
|
394
|
-
this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}, ${asClass}>`);
|
|
395
|
-
this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
|
|
396
|
-
this.writeln(`static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${asClass}>`);
|
|
397
|
-
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. */");
|
|
398
|
-
this.writeln("static toJsonSchema: () => any");
|
|
399
|
-
if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
|
|
400
|
-
this.writeln("static toExampleData?: () => any");
|
|
401
|
-
if (interfaceNode && this.hasDbTable(interfaceNode)) this.renderFlat(interfaceNode);
|
|
402
|
-
this.pop();
|
|
403
|
-
}
|
|
404
380
|
renderType(node) {
|
|
405
381
|
this.writeln();
|
|
406
382
|
const exported = node.token("export")?.text === "export";
|
|
@@ -498,6 +474,88 @@ else {
|
|
|
498
474
|
}
|
|
499
475
|
this.pop();
|
|
500
476
|
}
|
|
477
|
+
/**
|
|
478
|
+
* Renders the `static __pk` property — the primary key type for type-safe
|
|
479
|
+
* `deleteOne`/`findById` signatures on `AtscriptDbTable`.
|
|
480
|
+
*
|
|
481
|
+
* - **Single PK** (one `@meta.id`) → `static __pk: <scalar type>`
|
|
482
|
+
* - **Compound PK** (multiple `@meta.id`) → `static __pk: { field1: Type1; field2: Type2 }`
|
|
483
|
+
* - **No PK** → no `__pk` emitted (unless unique indexes exist)
|
|
484
|
+
* - **Unique indexes** (`@db.index.unique`) → appended as union members
|
|
485
|
+
* - **Mongo collection** → always includes `string` (ObjectId) in the union;
|
|
486
|
+
* if no `@meta.id` fields, `__pk` is just `string`
|
|
487
|
+
*/ renderPk(node) {
|
|
488
|
+
const isMongoCollection = !!node.annotations?.some((a) => a.name === "db.mongo.collection");
|
|
489
|
+
let struct;
|
|
490
|
+
if (node.hasExtends) struct = this.doc.resolveInterfaceExtends(node);
|
|
491
|
+
if (!struct) struct = node.getDefinition();
|
|
492
|
+
if (!struct || !(0, __atscript_core.isStructure)(struct)) return;
|
|
493
|
+
const structNode = struct;
|
|
494
|
+
const pkProps = [];
|
|
495
|
+
const uniqueByIndex = new Map();
|
|
496
|
+
for (const [name, prop] of structNode.props) {
|
|
497
|
+
if (prop.token("identifier")?.pattern) continue;
|
|
498
|
+
if (isMongoCollection && name === "_id") continue;
|
|
499
|
+
if (prop.countAnnotations("meta.id") > 0) pkProps.push({
|
|
500
|
+
name,
|
|
501
|
+
prop
|
|
502
|
+
});
|
|
503
|
+
if (prop.annotations) {
|
|
504
|
+
for (const ann of prop.annotations) if (ann.name === "db.index.unique") {
|
|
505
|
+
const indexName = ann.args[0]?.text ?? name;
|
|
506
|
+
let group = uniqueByIndex.get(indexName);
|
|
507
|
+
if (!group) {
|
|
508
|
+
group = [];
|
|
509
|
+
uniqueByIndex.set(indexName, group);
|
|
510
|
+
}
|
|
511
|
+
group.push({
|
|
512
|
+
name,
|
|
513
|
+
prop
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
const uniqueProps = [];
|
|
519
|
+
const pkNames = new Set(pkProps.map((p) => p.name));
|
|
520
|
+
for (const fields of uniqueByIndex.values()) if (fields.length === 1 && !pkNames.has(fields[0].name)) uniqueProps.push(fields[0]);
|
|
521
|
+
if (pkProps.length === 0 && uniqueProps.length === 0 && !isMongoCollection) return;
|
|
522
|
+
let mongoIdType;
|
|
523
|
+
if (isMongoCollection) {
|
|
524
|
+
const idProp = structNode.props.get("_id");
|
|
525
|
+
if (idProp) mongoIdType = this.renderTypeDefString(idProp.getDefinition()).trim();
|
|
526
|
+
mongoIdType ?? (mongoIdType = "string");
|
|
527
|
+
}
|
|
528
|
+
const uniqueTypes = [];
|
|
529
|
+
const seenTypes = new Set();
|
|
530
|
+
for (const { prop } of uniqueProps) {
|
|
531
|
+
const rendered = this.renderTypeDefString(prop.getDefinition()).trim();
|
|
532
|
+
if (!seenTypes.has(rendered)) {
|
|
533
|
+
seenTypes.add(rendered);
|
|
534
|
+
uniqueTypes.push(rendered);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
this.writeln();
|
|
538
|
+
const uniqueSuffix = uniqueTypes.length > 0 ? ` | ${uniqueTypes.join(" | ")}` : "";
|
|
539
|
+
if (pkProps.length === 0 && !isMongoCollection) this.writeln(`static __pk: ${uniqueTypes.join(" | ")}`);
|
|
540
|
+
else if (pkProps.length === 0) this.writeln(`static __pk: ${mongoIdType}${uniqueSuffix}`);
|
|
541
|
+
else if (pkProps.length === 1) {
|
|
542
|
+
this.write("static __pk: ");
|
|
543
|
+
if (isMongoCollection) this.write(`${mongoIdType} | `);
|
|
544
|
+
const renderedDef = this.renderTypeDefString(pkProps[0].prop.getDefinition()).trim();
|
|
545
|
+
this.writeln(`${renderedDef}${uniqueSuffix}`);
|
|
546
|
+
} else {
|
|
547
|
+
this.write("static __pk: ");
|
|
548
|
+
if (isMongoCollection) this.write(`${mongoIdType} | `);
|
|
549
|
+
this.blockln("{}");
|
|
550
|
+
for (const { name, prop } of pkProps) {
|
|
551
|
+
this.write(wrapProp(name), ": ");
|
|
552
|
+
const renderedDef = this.renderTypeDefString(prop.getDefinition());
|
|
553
|
+
renderedDef.split("\n").forEach((l) => this.writeln(l));
|
|
554
|
+
}
|
|
555
|
+
this.pop();
|
|
556
|
+
this.writeln(uniqueSuffix);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
501
559
|
phantomPropType(def) {
|
|
502
560
|
if (!def) return undefined;
|
|
503
561
|
if ((0, __atscript_core.isPrimitive)(def) && def.config.type === "phantom") return def.id;
|
|
@@ -546,24 +604,6 @@ function renderPrimitiveTypeDef(def) {
|
|
|
546
604
|
}
|
|
547
605
|
}
|
|
548
606
|
|
|
549
|
-
//#endregion
|
|
550
|
-
//#region packages/typescript/src/traverse.ts
|
|
551
|
-
function forAnnotatedType(def, handlers) {
|
|
552
|
-
switch (def.type.kind) {
|
|
553
|
-
case "": {
|
|
554
|
-
const typed = def;
|
|
555
|
-
if (handlers.phantom && typed.type.designType === "phantom") return handlers.phantom(typed);
|
|
556
|
-
return handlers.final(typed);
|
|
557
|
-
}
|
|
558
|
-
case "object": return handlers.object(def);
|
|
559
|
-
case "array": return handlers.array(def);
|
|
560
|
-
case "union": return handlers.union(def);
|
|
561
|
-
case "intersection": return handlers.intersection(def);
|
|
562
|
-
case "tuple": return handlers.tuple(def);
|
|
563
|
-
default: throw new Error(`Unknown type kind "${def.type.kind}"`);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
607
|
//#endregion
|
|
568
608
|
//#region packages/typescript/src/validator.ts
|
|
569
609
|
function _define_property$1(obj, key, value) {
|
|
@@ -579,28 +619,35 @@ else obj[key] = value;
|
|
|
579
619
|
const regexCache = new Map();
|
|
580
620
|
var Validator = class {
|
|
581
621
|
isLimitExceeded() {
|
|
582
|
-
if (this.stackErrors.length > 0)
|
|
622
|
+
if (this.stackErrors.length > 0) {
|
|
623
|
+
const top = this.stackErrors[this.stackErrors.length - 1];
|
|
624
|
+
return top !== null && top.length >= this.opts.errorLimit;
|
|
625
|
+
}
|
|
583
626
|
return this.errors.length >= this.opts.errorLimit;
|
|
584
627
|
}
|
|
585
628
|
push(name) {
|
|
586
629
|
this.stackPath.push(name);
|
|
587
|
-
this.stackErrors.push(
|
|
630
|
+
this.stackErrors.push(null);
|
|
631
|
+
this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
|
|
588
632
|
}
|
|
589
633
|
pop(saveErrors) {
|
|
590
634
|
this.stackPath.pop();
|
|
591
635
|
const popped = this.stackErrors.pop();
|
|
592
|
-
if (saveErrors && popped
|
|
593
|
-
|
|
594
|
-
});
|
|
636
|
+
if (saveErrors && popped !== null && popped !== undefined && popped.length > 0) for (const err of popped) this.error(err.message, err.path, err.details);
|
|
637
|
+
this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
|
|
595
638
|
return popped;
|
|
596
639
|
}
|
|
597
640
|
clear() {
|
|
598
|
-
this.stackErrors[this.stackErrors.length - 1] =
|
|
641
|
+
this.stackErrors[this.stackErrors.length - 1] = null;
|
|
599
642
|
}
|
|
600
643
|
error(message, path$2, details) {
|
|
601
|
-
|
|
644
|
+
let errors = this.stackErrors[this.stackErrors.length - 1];
|
|
645
|
+
if (!errors) if (this.stackErrors.length > 0) {
|
|
646
|
+
errors = [];
|
|
647
|
+
this.stackErrors[this.stackErrors.length - 1] = errors;
|
|
648
|
+
} else errors = this.errors;
|
|
602
649
|
const error = {
|
|
603
|
-
path: path$2 || this.
|
|
650
|
+
path: path$2 || this.cachedPath,
|
|
604
651
|
message
|
|
605
652
|
};
|
|
606
653
|
if (details?.length) error.details = details;
|
|
@@ -620,9 +667,10 @@ var Validator = class {
|
|
|
620
667
|
* @returns `true` if the value matches the type definition.
|
|
621
668
|
* @throws {ValidatorError} When validation fails and `safe` is not `true`.
|
|
622
669
|
*/ validate(value, safe, context) {
|
|
623
|
-
this.push("");
|
|
624
670
|
this.errors = [];
|
|
625
671
|
this.stackErrors = [];
|
|
672
|
+
this.stackPath = [""];
|
|
673
|
+
this.cachedPath = "";
|
|
626
674
|
this.context = context;
|
|
627
675
|
const passed = this.validateSafe(this.def, value);
|
|
628
676
|
this.pop(!passed);
|
|
@@ -636,7 +684,7 @@ var Validator = class {
|
|
|
636
684
|
validateSafe(def, value) {
|
|
637
685
|
if (this.isLimitExceeded()) return false;
|
|
638
686
|
if (!isAnnotatedType(def)) throw new Error("Can not validate not-annotated type");
|
|
639
|
-
if (typeof this.opts.replace === "function") def = this.opts.replace(def, this.
|
|
687
|
+
if (typeof this.opts.replace === "function") def = this.opts.replace(def, this.cachedPath);
|
|
640
688
|
if (def.optional && value === undefined) return true;
|
|
641
689
|
for (const plugin of this.opts.plugins) {
|
|
642
690
|
const result = plugin(this, def, value);
|
|
@@ -645,18 +693,21 @@ var Validator = class {
|
|
|
645
693
|
return this.validateAnnotatedType(def, value);
|
|
646
694
|
}
|
|
647
695
|
get path() {
|
|
648
|
-
return this.
|
|
696
|
+
return this.cachedPath;
|
|
649
697
|
}
|
|
650
698
|
validateAnnotatedType(def, value) {
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
699
|
+
switch (def.type.kind) {
|
|
700
|
+
case "": {
|
|
701
|
+
if (def.type.designType === "phantom") return true;
|
|
702
|
+
return this.validatePrimitive(def, value);
|
|
703
|
+
}
|
|
704
|
+
case "object": return this.validateObject(def, value);
|
|
705
|
+
case "array": return this.validateArray(def, value);
|
|
706
|
+
case "union": return this.validateUnion(def, value);
|
|
707
|
+
case "intersection": return this.validateIntersection(def, value);
|
|
708
|
+
case "tuple": return this.validateTuple(def, value);
|
|
709
|
+
default: throw new Error(`Unknown type kind "${def.type.kind}"`);
|
|
710
|
+
}
|
|
660
711
|
}
|
|
661
712
|
validateUnion(def, value) {
|
|
662
713
|
let i = 0;
|
|
@@ -720,6 +771,30 @@ var Validator = class {
|
|
|
720
771
|
return false;
|
|
721
772
|
}
|
|
722
773
|
}
|
|
774
|
+
const uniqueItems = def.metadata.get("expect.array.uniqueItems");
|
|
775
|
+
if (uniqueItems) {
|
|
776
|
+
const separator = "▼↩";
|
|
777
|
+
const seen = new Set();
|
|
778
|
+
const keyProps = new Set();
|
|
779
|
+
if (def.type.of.type.kind === "object") {
|
|
780
|
+
for (const [key, val] of def.type.of.type.props.entries()) if (val.metadata.get("expect.array.key")) keyProps.add(key);
|
|
781
|
+
}
|
|
782
|
+
for (let idx = 0; idx < value.length; idx++) {
|
|
783
|
+
const item = value[idx];
|
|
784
|
+
let key;
|
|
785
|
+
if (keyProps.size > 0) {
|
|
786
|
+
key = "";
|
|
787
|
+
for (const prop of keyProps) key += JSON.stringify(item[prop]) + separator;
|
|
788
|
+
} else key = JSON.stringify(item);
|
|
789
|
+
if (seen.has(key)) {
|
|
790
|
+
this.push(String(idx));
|
|
791
|
+
this.error(uniqueItems.message || "Duplicate items are not allowed");
|
|
792
|
+
this.pop(true);
|
|
793
|
+
return false;
|
|
794
|
+
}
|
|
795
|
+
seen.add(key);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
723
798
|
let i = 0;
|
|
724
799
|
let passed = true;
|
|
725
800
|
for (const item of value) {
|
|
@@ -741,21 +816,20 @@ var Validator = class {
|
|
|
741
816
|
let passed = true;
|
|
742
817
|
const valueKeys = new Set(Object.keys(value));
|
|
743
818
|
const typeKeys = new Set();
|
|
744
|
-
|
|
819
|
+
let skipList;
|
|
745
820
|
if (this.opts.skipList) {
|
|
746
|
-
const path$2 = this.stackPath.length > 1 ? `${this.
|
|
747
|
-
this.opts.skipList.
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
});
|
|
821
|
+
const path$2 = this.stackPath.length > 1 ? `${this.cachedPath}.` : "";
|
|
822
|
+
for (const item of this.opts.skipList) if (item.startsWith(path$2)) {
|
|
823
|
+
const key = item.slice(path$2.length);
|
|
824
|
+
if (!skipList) skipList = new Set();
|
|
825
|
+
skipList.add(key);
|
|
826
|
+
valueKeys.delete(key);
|
|
827
|
+
}
|
|
754
828
|
}
|
|
755
829
|
let partialFunctionMatched = false;
|
|
756
|
-
if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.
|
|
830
|
+
if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.cachedPath);
|
|
757
831
|
for (const [key, item] of def.type.props.entries()) {
|
|
758
|
-
if (skipList.has(key) || isPhantomType(item)) continue;
|
|
832
|
+
if (skipList && skipList.has(key) || isPhantomType(item)) continue;
|
|
759
833
|
typeKeys.add(key);
|
|
760
834
|
if (value[key] === undefined) {
|
|
761
835
|
if (partialFunctionMatched || this.opts.partial === "deep" || this.opts.partial === true && this.stackPath.length <= 1) continue;
|
|
@@ -776,19 +850,21 @@ else {
|
|
|
776
850
|
def: propDef
|
|
777
851
|
});
|
|
778
852
|
if (matched.length > 0) {
|
|
853
|
+
this.push(key);
|
|
779
854
|
let keyPassed = false;
|
|
780
|
-
for (const { def:
|
|
781
|
-
this.
|
|
782
|
-
|
|
783
|
-
|
|
855
|
+
for (const { def: propDef } of matched) {
|
|
856
|
+
if (this.validateSafe(propDef, value[key])) {
|
|
857
|
+
keyPassed = true;
|
|
858
|
+
break;
|
|
859
|
+
}
|
|
860
|
+
this.clear();
|
|
784
861
|
}
|
|
785
862
|
if (!keyPassed) {
|
|
786
|
-
this.push(key);
|
|
787
863
|
this.validateSafe(matched[0].def, value[key]);
|
|
788
864
|
this.pop(true);
|
|
789
865
|
passed = false;
|
|
790
866
|
if (this.isLimitExceeded()) return false;
|
|
791
|
-
}
|
|
867
|
+
} else this.pop(false);
|
|
792
868
|
} else if (this.opts.unknownProps !== "ignore") {
|
|
793
869
|
if (this.opts.unknownProps === "error") {
|
|
794
870
|
this.push(key);
|
|
@@ -941,11 +1017,13 @@ else {
|
|
|
941
1017
|
/** Validation errors collected during the last {@link validate} call. */ _define_property$1(this, "errors", void 0);
|
|
942
1018
|
_define_property$1(this, "stackErrors", void 0);
|
|
943
1019
|
_define_property$1(this, "stackPath", void 0);
|
|
1020
|
+
_define_property$1(this, "cachedPath", void 0);
|
|
944
1021
|
_define_property$1(this, "context", void 0);
|
|
945
1022
|
this.def = def;
|
|
946
1023
|
this.errors = [];
|
|
947
1024
|
this.stackErrors = [];
|
|
948
1025
|
this.stackPath = [];
|
|
1026
|
+
this.cachedPath = "";
|
|
949
1027
|
this.opts = {
|
|
950
1028
|
partial: false,
|
|
951
1029
|
unknownProps: "error",
|
|
@@ -963,6 +1041,24 @@ var ValidatorError = class extends Error {
|
|
|
963
1041
|
|
|
964
1042
|
//#endregion
|
|
965
1043
|
//#region packages/typescript/src/annotated-type.ts
|
|
1044
|
+
const COMPLEX_KINDS = new Set([
|
|
1045
|
+
"union",
|
|
1046
|
+
"intersection",
|
|
1047
|
+
"tuple"
|
|
1048
|
+
]);
|
|
1049
|
+
/** Shared validator method reused by all annotated type nodes. */ function validatorMethod(opts) {
|
|
1050
|
+
return new Validator(this, opts);
|
|
1051
|
+
}
|
|
1052
|
+
function createAnnotatedTypeNode(type, metadata, opts) {
|
|
1053
|
+
return {
|
|
1054
|
+
__is_atscript_annotated_type: true,
|
|
1055
|
+
type,
|
|
1056
|
+
metadata,
|
|
1057
|
+
validator: validatorMethod,
|
|
1058
|
+
id: opts?.id,
|
|
1059
|
+
optional: opts?.optional
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
966
1062
|
function isAnnotatedType(type) {
|
|
967
1063
|
return type && type.__is_atscript_annotated_type;
|
|
968
1064
|
}
|
|
@@ -979,38 +1075,24 @@ function defineAnnotatedType(_kind, base) {
|
|
|
979
1075
|
const kind = _kind || "";
|
|
980
1076
|
const type = base?.type || {};
|
|
981
1077
|
type.kind = kind;
|
|
982
|
-
if ([
|
|
983
|
-
"union",
|
|
984
|
-
"intersection",
|
|
985
|
-
"tuple"
|
|
986
|
-
].includes(kind)) type.items = [];
|
|
1078
|
+
if (COMPLEX_KINDS.has(kind)) type.items = [];
|
|
987
1079
|
if (kind === "object") {
|
|
988
1080
|
type.props = new Map();
|
|
989
1081
|
type.propsPatterns = [];
|
|
990
1082
|
}
|
|
991
1083
|
type.tags = new Set();
|
|
992
1084
|
const metadata = base?.metadata || new Map();
|
|
993
|
-
|
|
994
|
-
__is_atscript_annotated_type: true,
|
|
995
|
-
metadata,
|
|
996
|
-
type,
|
|
997
|
-
validator(opts) {
|
|
998
|
-
return new Validator(this, opts);
|
|
999
|
-
}
|
|
1000
|
-
});
|
|
1001
|
-
else base = {
|
|
1085
|
+
const payload = {
|
|
1002
1086
|
__is_atscript_annotated_type: true,
|
|
1003
1087
|
metadata,
|
|
1004
1088
|
type,
|
|
1005
|
-
validator
|
|
1006
|
-
return new Validator(this, opts);
|
|
1007
|
-
}
|
|
1089
|
+
validator: validatorMethod
|
|
1008
1090
|
};
|
|
1091
|
+
base = base ? Object.assign(base, payload) : payload;
|
|
1009
1092
|
const handle = {
|
|
1010
1093
|
$type: base,
|
|
1011
1094
|
$def: type,
|
|
1012
1095
|
$metadata: metadata,
|
|
1013
|
-
_existingObject: undefined,
|
|
1014
1096
|
tags(...tags) {
|
|
1015
1097
|
for (const tag of tags) this.$def.tags.add(tag);
|
|
1016
1098
|
return this;
|
|
@@ -1051,27 +1133,18 @@ else base = {
|
|
|
1051
1133
|
return this;
|
|
1052
1134
|
},
|
|
1053
1135
|
refTo(type$1, chain) {
|
|
1136
|
+
if (!isAnnotatedType(type$1)) throw new Error(`${type$1} is not annotated type`);
|
|
1054
1137
|
let newBase = type$1;
|
|
1055
1138
|
const typeName = type$1.name || "Unknown";
|
|
1056
|
-
if (
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1139
|
+
if (chain) for (let i = 0; i < chain.length; i++) {
|
|
1140
|
+
const c = chain[i];
|
|
1141
|
+
if (newBase.type.kind === "object" && newBase.type.props.has(c)) newBase = newBase.type.props.get(c);
|
|
1142
|
+
else {
|
|
1143
|
+
const keys = chain.slice(0, i + 1).map((k) => `["${k}"]`).join("");
|
|
1144
|
+
throw new Error(`Can't find prop ${typeName}${keys}`);
|
|
1062
1145
|
}
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
this.$type = {
|
|
1066
|
-
__is_atscript_annotated_type: true,
|
|
1067
|
-
type: newBase.type,
|
|
1068
|
-
metadata,
|
|
1069
|
-
id: newBase.id,
|
|
1070
|
-
validator(opts) {
|
|
1071
|
-
return new Validator(this, opts);
|
|
1072
|
-
}
|
|
1073
|
-
};
|
|
1074
|
-
} else throw new Error(`${type$1} is not annotated type`);
|
|
1146
|
+
}
|
|
1147
|
+
this.$type = createAnnotatedTypeNode(newBase.type, metadata, { id: newBase.id });
|
|
1075
1148
|
return this;
|
|
1076
1149
|
},
|
|
1077
1150
|
annotate(key, value, asArray) {
|
|
@@ -1089,6 +1162,24 @@ function isPhantomType(def) {
|
|
|
1089
1162
|
return def.type.kind === "" && def.type.designType === "phantom";
|
|
1090
1163
|
}
|
|
1091
1164
|
|
|
1165
|
+
//#endregion
|
|
1166
|
+
//#region packages/typescript/src/traverse.ts
|
|
1167
|
+
function forAnnotatedType(def, handlers) {
|
|
1168
|
+
switch (def.type.kind) {
|
|
1169
|
+
case "": {
|
|
1170
|
+
const typed = def;
|
|
1171
|
+
if (handlers.phantom && typed.type.designType === "phantom") return handlers.phantom(typed);
|
|
1172
|
+
return handlers.final(typed);
|
|
1173
|
+
}
|
|
1174
|
+
case "object": return handlers.object(def);
|
|
1175
|
+
case "array": return handlers.array(def);
|
|
1176
|
+
case "union": return handlers.union(def);
|
|
1177
|
+
case "intersection": return handlers.intersection(def);
|
|
1178
|
+
case "tuple": return handlers.tuple(def);
|
|
1179
|
+
default: throw new Error(`Unknown type kind "${def.type.kind}"`);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1092
1183
|
//#endregion
|
|
1093
1184
|
//#region packages/typescript/src/json-schema.ts
|
|
1094
1185
|
/**
|
|
@@ -1103,10 +1194,10 @@ function isPhantomType(def) {
|
|
|
1103
1194
|
const firstObj = items[0].type;
|
|
1104
1195
|
const candidates = [];
|
|
1105
1196
|
for (const [propName, propType] of firstObj.props.entries()) if (propType.type.kind === "" && propType.type.value !== undefined) candidates.push(propName);
|
|
1106
|
-
|
|
1197
|
+
let result = null;
|
|
1107
1198
|
for (const candidate of candidates) {
|
|
1108
1199
|
const values = new Set();
|
|
1109
|
-
const
|
|
1200
|
+
const indexMapping = {};
|
|
1110
1201
|
let valid = true;
|
|
1111
1202
|
for (let i = 0; i < items.length; i++) {
|
|
1112
1203
|
const obj = items[i].type;
|
|
@@ -1121,19 +1212,21 @@ function isPhantomType(def) {
|
|
|
1121
1212
|
break;
|
|
1122
1213
|
}
|
|
1123
1214
|
values.add(val);
|
|
1124
|
-
|
|
1215
|
+
indexMapping[String(val)] = i;
|
|
1216
|
+
}
|
|
1217
|
+
if (valid) {
|
|
1218
|
+
if (result) return null;
|
|
1219
|
+
result = {
|
|
1220
|
+
propertyName: candidate,
|
|
1221
|
+
indexMapping
|
|
1222
|
+
};
|
|
1125
1223
|
}
|
|
1126
|
-
if (valid) validCandidates.push({
|
|
1127
|
-
propertyName: candidate,
|
|
1128
|
-
mapping
|
|
1129
|
-
});
|
|
1130
1224
|
}
|
|
1131
|
-
|
|
1132
|
-
return null;
|
|
1225
|
+
return result;
|
|
1133
1226
|
}
|
|
1134
1227
|
function buildJsonSchema(type) {
|
|
1135
1228
|
const defs = {};
|
|
1136
|
-
let
|
|
1229
|
+
let hasDefs = false;
|
|
1137
1230
|
const buildObject = (d) => {
|
|
1138
1231
|
const properties = {};
|
|
1139
1232
|
const required = [];
|
|
@@ -1150,15 +1243,15 @@ function buildJsonSchema(type) {
|
|
|
1150
1243
|
return schema$1;
|
|
1151
1244
|
};
|
|
1152
1245
|
const build = (def) => {
|
|
1153
|
-
if (def.id && def.type.kind === "object" &&
|
|
1246
|
+
if (def.id && def.type.kind === "object" && def !== type) {
|
|
1154
1247
|
const name = def.id;
|
|
1155
1248
|
if (!defs[name]) {
|
|
1249
|
+
hasDefs = true;
|
|
1156
1250
|
defs[name] = {};
|
|
1157
1251
|
defs[name] = buildObject(def);
|
|
1158
1252
|
}
|
|
1159
1253
|
return { $ref: `#/$defs/${name}` };
|
|
1160
1254
|
}
|
|
1161
|
-
isRoot = false;
|
|
1162
1255
|
const meta = def.metadata;
|
|
1163
1256
|
return forAnnotatedType(def, {
|
|
1164
1257
|
phantom() {
|
|
@@ -1183,11 +1276,9 @@ function buildJsonSchema(type) {
|
|
|
1183
1276
|
if (disc) {
|
|
1184
1277
|
const oneOf = d.type.items.map(build);
|
|
1185
1278
|
const mapping = {};
|
|
1186
|
-
for (const [val,
|
|
1187
|
-
const idx = Number.parseInt(origPath.split("/").pop(), 10);
|
|
1279
|
+
for (const [val, idx] of Object.entries(disc.indexMapping)) {
|
|
1188
1280
|
const item = d.type.items[idx];
|
|
1189
|
-
|
|
1190
|
-
else mapping[val] = origPath;
|
|
1281
|
+
mapping[val] = item.id && defs[item.id] ? `#/$defs/${item.id}` : `#/oneOf/${idx}`;
|
|
1191
1282
|
}
|
|
1192
1283
|
return {
|
|
1193
1284
|
oneOf,
|
|
@@ -1237,7 +1328,7 @@ else schema$1.allOf = (schema$1.allOf || []).concat(patterns.map((p) => ({ patte
|
|
|
1237
1328
|
});
|
|
1238
1329
|
};
|
|
1239
1330
|
const schema = build(type);
|
|
1240
|
-
if (
|
|
1331
|
+
if (hasDefs) return {
|
|
1241
1332
|
...schema,
|
|
1242
1333
|
$defs: defs
|
|
1243
1334
|
};
|
|
@@ -1261,24 +1352,25 @@ var JsRenderer = class extends BaseRenderer {
|
|
|
1261
1352
|
this.writeln("// prettier-ignore-start");
|
|
1262
1353
|
this.writeln("/* eslint-disable */");
|
|
1263
1354
|
this.writeln("/* oxlint-disable */");
|
|
1355
|
+
let hasMutatingAnnotate = false;
|
|
1356
|
+
const nodesByName = new Map();
|
|
1357
|
+
for (const node of this.doc.nodes) {
|
|
1358
|
+
if (node.entity === "annotate" && node.isMutating) hasMutatingAnnotate = true;
|
|
1359
|
+
if (node.__typeId !== null && node.__typeId !== undefined && node.id) {
|
|
1360
|
+
const name = node.id;
|
|
1361
|
+
if (!nodesByName.has(name)) nodesByName.set(name, []);
|
|
1362
|
+
nodesByName.get(name).push(node);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
for (const [name, nodes] of nodesByName) if (nodes.length === 1) this.typeIds.set(nodes[0], name);
|
|
1366
|
+
else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}__${i + 1}`);
|
|
1264
1367
|
const imports = ["defineAnnotatedType as $", "annotate as $a"];
|
|
1265
|
-
const hasMutatingAnnotate = this.doc.nodes.some((n) => n.entity === "annotate" && n.isMutating);
|
|
1266
1368
|
if (hasMutatingAnnotate) imports.push("cloneRefProp as $c");
|
|
1267
1369
|
const jsonSchemaMode = resolveJsonSchemaMode(this.opts);
|
|
1268
1370
|
if (jsonSchemaMode === "lazy") imports.push("buildJsonSchema as $$");
|
|
1269
1371
|
if (this.opts?.exampleData) imports.push("createDataFromAnnotatedType as $e");
|
|
1270
1372
|
if (jsonSchemaMode === false) imports.push("throwFeatureDisabled as $d");
|
|
1271
1373
|
this.writeln(`import { ${imports.join(", ")} } from "@atscript/typescript/utils"`);
|
|
1272
|
-
const nameCounts = new Map();
|
|
1273
|
-
const nodesByName = new Map();
|
|
1274
|
-
for (const node of this.doc.nodes) if (node.__typeId !== null && node.__typeId !== undefined && node.id) {
|
|
1275
|
-
const name = node.id;
|
|
1276
|
-
nameCounts.set(name, (nameCounts.get(name) || 0) + 1);
|
|
1277
|
-
if (!nodesByName.has(name)) nodesByName.set(name, []);
|
|
1278
|
-
nodesByName.get(name).push(node);
|
|
1279
|
-
}
|
|
1280
|
-
for (const [name, nodes] of nodesByName) if (nodes.length === 1) this.typeIds.set(nodes[0], name);
|
|
1281
|
-
else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}__${i + 1}`);
|
|
1282
1374
|
}
|
|
1283
1375
|
buildAdHocMap(annotateNodes) {
|
|
1284
1376
|
const map = new Map();
|
|
@@ -1287,7 +1379,8 @@ else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}_
|
|
|
1287
1379
|
const anns = entry.annotations || [];
|
|
1288
1380
|
if (anns.length > 0) {
|
|
1289
1381
|
const existing = map.get(path$2);
|
|
1290
|
-
|
|
1382
|
+
if (existing) existing.push(...anns);
|
|
1383
|
+
else map.set(path$2, [...anns]);
|
|
1291
1384
|
}
|
|
1292
1385
|
}
|
|
1293
1386
|
return map.size > 0 ? map : null;
|
|
@@ -1346,35 +1439,20 @@ else def = def.getDefinition() || def;
|
|
|
1346
1439
|
this.renderExampleDataMethod(node);
|
|
1347
1440
|
}
|
|
1348
1441
|
renderInterface(node) {
|
|
1349
|
-
this.
|
|
1350
|
-
const exported = node.token("export")?.text === "export";
|
|
1351
|
-
this.write(exported ? "export " : "");
|
|
1352
|
-
this.write(`class ${node.id} `);
|
|
1353
|
-
this.blockln("{}");
|
|
1354
|
-
this.renderClassStatics(node);
|
|
1355
|
-
this.popln();
|
|
1356
|
-
this.postAnnotate.push(node);
|
|
1357
|
-
this.writeln();
|
|
1442
|
+
this.renderDefinitionClass(node);
|
|
1358
1443
|
}
|
|
1359
1444
|
renderType(node) {
|
|
1360
|
-
this.
|
|
1361
|
-
const exported = node.token("export")?.text === "export";
|
|
1362
|
-
this.write(exported ? "export " : "");
|
|
1363
|
-
this.write(`class ${node.id} `);
|
|
1364
|
-
this.blockln("{}");
|
|
1365
|
-
this.renderClassStatics(node);
|
|
1366
|
-
this.popln();
|
|
1367
|
-
this.postAnnotate.push(node);
|
|
1368
|
-
this.writeln();
|
|
1445
|
+
this.renderDefinitionClass(node);
|
|
1369
1446
|
}
|
|
1370
1447
|
renderAnnotate(node) {
|
|
1371
1448
|
if (node.isMutating) {
|
|
1372
1449
|
this.postAnnotate.push(node);
|
|
1373
1450
|
return;
|
|
1374
1451
|
}
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1452
|
+
if (!this.doc.unwindType(node.targetName)?.def) return;
|
|
1453
|
+
this.renderDefinitionClass(node);
|
|
1454
|
+
}
|
|
1455
|
+
renderDefinitionClass(node) {
|
|
1378
1456
|
this.writeln();
|
|
1379
1457
|
const exported = node.token("export")?.text === "export";
|
|
1380
1458
|
this.write(exported ? "export " : "");
|
|
@@ -1532,6 +1610,11 @@ else handle.prop(prop.id, propHandle.$type);
|
|
|
1532
1610
|
handle.annotate(a.name, true);
|
|
1533
1611
|
break;
|
|
1534
1612
|
}
|
|
1613
|
+
case "expect.array.uniqueItems":
|
|
1614
|
+
case "expect.array.key": {
|
|
1615
|
+
handle.annotate(a.name, { message: a.args[0]?.text });
|
|
1616
|
+
break;
|
|
1617
|
+
}
|
|
1535
1618
|
default:
|
|
1536
1619
|
}
|
|
1537
1620
|
});
|
|
@@ -1599,10 +1682,7 @@ else handle.prop(prop.id, propHandle.$type);
|
|
|
1599
1682
|
this.writeln(`$("array"${name ? `, ${name}` : ""})`).indent().defineArray(node).unindent();
|
|
1600
1683
|
return this;
|
|
1601
1684
|
}
|
|
1602
|
-
default:
|
|
1603
|
-
console.log("!!!!!!! UNKNOWN", node.entity);
|
|
1604
|
-
return this;
|
|
1605
|
-
}
|
|
1685
|
+
default: return this;
|
|
1606
1686
|
}
|
|
1607
1687
|
}
|
|
1608
1688
|
defineConst(node) {
|
|
@@ -1814,40 +1894,25 @@ else targetValue = "true";
|
|
|
1814
1894
|
this.writeln(`$c(${clone.parentPath}, "${escapeQuotes(clone.propName)}")`);
|
|
1815
1895
|
}
|
|
1816
1896
|
}
|
|
1817
|
-
for (const { entry, accessors } of entryAccessors)
|
|
1818
|
-
const anns = entry.annotations;
|
|
1819
|
-
for (const accessor of accessors) {
|
|
1820
|
-
const cleared = new Set();
|
|
1821
|
-
for (const an of anns) {
|
|
1822
|
-
const { value, multiple } = this.computeAnnotationValue(entry, an);
|
|
1823
|
-
if (multiple) {
|
|
1824
|
-
if (!cleared.has(an.name)) {
|
|
1825
|
-
const spec = this.doc.resolveAnnotation(an.name);
|
|
1826
|
-
if (!spec || spec.config.mergeStrategy !== "append") this.writeln(`${accessor}.metadata.delete("${escapeQuotes(an.name)}")`);
|
|
1827
|
-
cleared.add(an.name);
|
|
1828
|
-
}
|
|
1829
|
-
this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value}, true)`);
|
|
1830
|
-
} else this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value})`);
|
|
1831
|
-
}
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1897
|
+
for (const { entry, accessors } of entryAccessors) for (const accessor of accessors) this.emitMutatingAnnotations(entry, entry.annotations, accessor);
|
|
1834
1898
|
const topAnnotations = node.annotations;
|
|
1835
|
-
if (topAnnotations && topAnnotations.length > 0)
|
|
1836
|
-
const cleared = new Set();
|
|
1837
|
-
for (const an of topAnnotations) {
|
|
1838
|
-
const { value, multiple } = this.computeAnnotationValue(node, an);
|
|
1839
|
-
if (multiple) {
|
|
1840
|
-
if (!cleared.has(an.name)) {
|
|
1841
|
-
const spec = this.doc.resolveAnnotation(an.name);
|
|
1842
|
-
if (!spec || spec.config.mergeStrategy !== "append") this.writeln(`${targetName}.metadata.delete("${escapeQuotes(an.name)}")`);
|
|
1843
|
-
cleared.add(an.name);
|
|
1844
|
-
}
|
|
1845
|
-
this.writeln(`$a(${targetName}.metadata, "${escapeQuotes(an.name)}", ${value}, true)`);
|
|
1846
|
-
} else this.writeln(`$a(${targetName}.metadata, "${escapeQuotes(an.name)}", ${value})`);
|
|
1847
|
-
}
|
|
1848
|
-
}
|
|
1899
|
+
if (topAnnotations && topAnnotations.length > 0) this.emitMutatingAnnotations(node, topAnnotations, targetName);
|
|
1849
1900
|
this.writeln();
|
|
1850
1901
|
}
|
|
1902
|
+
emitMutatingAnnotations(node, annotations, accessor) {
|
|
1903
|
+
const cleared = new Set();
|
|
1904
|
+
for (const an of annotations) {
|
|
1905
|
+
const { value, multiple } = this.computeAnnotationValue(node, an);
|
|
1906
|
+
if (multiple) {
|
|
1907
|
+
if (!cleared.has(an.name)) {
|
|
1908
|
+
const spec = this.doc.resolveAnnotation(an.name);
|
|
1909
|
+
if (!spec || spec.config.mergeStrategy !== "append") this.writeln(`${accessor}.metadata.delete("${escapeQuotes(an.name)}")`);
|
|
1910
|
+
cleared.add(an.name);
|
|
1911
|
+
}
|
|
1912
|
+
this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value}, true)`);
|
|
1913
|
+
} else this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value})`);
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1851
1916
|
resolveTargetDef(targetName) {
|
|
1852
1917
|
const unwound = this.doc.unwindType(targetName);
|
|
1853
1918
|
if (!unwound?.def) return undefined;
|