@atscript/typescript 0.0.27 → 0.0.28
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 +689 -15
- package/dist/index.cjs +586 -361
- package/dist/index.d.ts +11 -2
- package/dist/index.mjs +586 -362
- package/package.json +2 -2
package/dist/cli.cjs
CHANGED
|
@@ -29,7 +29,7 @@ const __atscript_core = __toESM(require("@atscript/core"));
|
|
|
29
29
|
const fs = __toESM(require("fs"));
|
|
30
30
|
|
|
31
31
|
//#region packages/typescript/src/codegen/code-printer.ts
|
|
32
|
-
function _define_property$
|
|
32
|
+
function _define_property$5(obj, key, value) {
|
|
33
33
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
34
34
|
value,
|
|
35
35
|
enumerable: true,
|
|
@@ -122,17 +122,17 @@ else this.write(closing);
|
|
|
122
122
|
return " ".repeat(this.indentLevel * this.indentSize);
|
|
123
123
|
}
|
|
124
124
|
constructor() {
|
|
125
|
-
_define_property$
|
|
126
|
-
_define_property$
|
|
127
|
-
_define_property$
|
|
128
|
-
_define_property$
|
|
129
|
-
_define_property$
|
|
125
|
+
_define_property$5(this, "lines", []);
|
|
126
|
+
_define_property$5(this, "currentLine", "");
|
|
127
|
+
_define_property$5(this, "indentLevel", 0);
|
|
128
|
+
_define_property$5(this, "indentSize", 2);
|
|
129
|
+
_define_property$5(this, "blockStack", []);
|
|
130
130
|
}
|
|
131
131
|
};
|
|
132
132
|
|
|
133
133
|
//#endregion
|
|
134
134
|
//#region packages/typescript/src/codegen/base-renderer.ts
|
|
135
|
-
function _define_property$
|
|
135
|
+
function _define_property$4(obj, key, value) {
|
|
136
136
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
137
137
|
value,
|
|
138
138
|
enumerable: true,
|
|
@@ -191,7 +191,7 @@ var BaseRenderer = class extends CodePrinter {
|
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
constructor(doc) {
|
|
194
|
-
super(), _define_property$
|
|
194
|
+
super(), _define_property$4(this, "doc", void 0), _define_property$4(this, "unused", void 0), this.doc = doc;
|
|
195
195
|
this.unused = new Set(this.doc.getUnusedTokens().map((t) => t.text));
|
|
196
196
|
}
|
|
197
197
|
};
|
|
@@ -209,6 +209,16 @@ function escapeQuotes(str) {
|
|
|
209
209
|
|
|
210
210
|
//#endregion
|
|
211
211
|
//#region packages/typescript/src/codegen/type-renderer.ts
|
|
212
|
+
function _define_property$3(obj, key, value) {
|
|
213
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
214
|
+
value,
|
|
215
|
+
enumerable: true,
|
|
216
|
+
configurable: true,
|
|
217
|
+
writable: true
|
|
218
|
+
});
|
|
219
|
+
else obj[key] = value;
|
|
220
|
+
return obj;
|
|
221
|
+
}
|
|
212
222
|
var TypeRenderer = class extends BaseRenderer {
|
|
213
223
|
pre() {
|
|
214
224
|
this.writeln("// prettier-ignore-start");
|
|
@@ -306,6 +316,7 @@ else if (patterns.length === 1) {
|
|
|
306
316
|
this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}>`);
|
|
307
317
|
this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
|
|
308
318
|
this.writeln(`static validator: <TT extends TAtscriptAnnotatedTypeConstructor = ${asClass}>(opts?: Partial<TValidatorOptions>) => Validator<TT>`);
|
|
319
|
+
this.writeln("static toJsonSchema: () => any");
|
|
309
320
|
}
|
|
310
321
|
this.pop();
|
|
311
322
|
}
|
|
@@ -348,6 +359,7 @@ else if ((0, __atscript_core.isPrimitive)(realDef)) typeDef = "TAtscriptTypeFina
|
|
|
348
359
|
this.writeln(`const type: ${typeDef}`);
|
|
349
360
|
this.writeln(`const metadata: TMetadataMap<AtscriptMetadata>`);
|
|
350
361
|
this.writeln(`const validator: <TT extends TAtscriptAnnotatedTypeConstructor = ${node.id}>(opts?: Partial<TValidatorOptions>) => Validator<TT>`);
|
|
362
|
+
if (this.opts?.jsonSchema) this.writeln("const toJsonSchema: () => any");
|
|
351
363
|
this.popln();
|
|
352
364
|
}
|
|
353
365
|
renderJsDoc(node) {
|
|
@@ -358,6 +370,9 @@ else if ((0, __atscript_core.isPrimitive)(realDef)) typeDef = "TAtscriptTypeFina
|
|
|
358
370
|
this.writeln(` * @see {@link ./${this.doc.name}${rangeStr}}`);
|
|
359
371
|
this.writeln(` */`);
|
|
360
372
|
}
|
|
373
|
+
constructor(doc, opts) {
|
|
374
|
+
super(doc), _define_property$3(this, "opts", void 0), this.opts = opts;
|
|
375
|
+
}
|
|
361
376
|
};
|
|
362
377
|
function renderPrimitiveTypeDef(def) {
|
|
363
378
|
if (!def) return "unknown";
|
|
@@ -376,6 +391,537 @@ function renderPrimitiveTypeDef(def) {
|
|
|
376
391
|
}
|
|
377
392
|
}
|
|
378
393
|
|
|
394
|
+
//#endregion
|
|
395
|
+
//#region packages/typescript/src/validator.ts
|
|
396
|
+
function _define_property$2(obj, key, value) {
|
|
397
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
398
|
+
value,
|
|
399
|
+
enumerable: true,
|
|
400
|
+
configurable: true,
|
|
401
|
+
writable: true
|
|
402
|
+
});
|
|
403
|
+
else obj[key] = value;
|
|
404
|
+
return obj;
|
|
405
|
+
}
|
|
406
|
+
const regexCache = new Map();
|
|
407
|
+
var Validator = class {
|
|
408
|
+
isLimitExceeded() {
|
|
409
|
+
if (this.stackErrors.length > 0) return this.stackErrors[this.stackErrors.length - 1].length >= this.opts.errorLimit;
|
|
410
|
+
return this.errors.length >= this.opts.errorLimit;
|
|
411
|
+
}
|
|
412
|
+
push(name) {
|
|
413
|
+
this.stackPath.push(name);
|
|
414
|
+
this.stackErrors.push([]);
|
|
415
|
+
}
|
|
416
|
+
pop(saveErrors) {
|
|
417
|
+
this.stackPath.pop();
|
|
418
|
+
const popped = this.stackErrors.pop();
|
|
419
|
+
if (saveErrors && popped?.length) popped.forEach((error) => {
|
|
420
|
+
this.error(error.message, error.path, error.details);
|
|
421
|
+
});
|
|
422
|
+
return popped;
|
|
423
|
+
}
|
|
424
|
+
clear() {
|
|
425
|
+
this.stackErrors[this.stackErrors.length - 1] = [];
|
|
426
|
+
}
|
|
427
|
+
error(message, path$3, details) {
|
|
428
|
+
const errors = this.stackErrors[this.stackErrors.length - 1] || this.errors;
|
|
429
|
+
const error = {
|
|
430
|
+
path: path$3 || this.path,
|
|
431
|
+
message
|
|
432
|
+
};
|
|
433
|
+
if (details?.length) error.details = details;
|
|
434
|
+
errors.push(error);
|
|
435
|
+
}
|
|
436
|
+
throw() {
|
|
437
|
+
throw new ValidatorError(this.errors);
|
|
438
|
+
}
|
|
439
|
+
validate(value, safe) {
|
|
440
|
+
this.push("");
|
|
441
|
+
this.errors = [];
|
|
442
|
+
this.stackErrors = [];
|
|
443
|
+
const passed = this.validateSafe(this.def, value);
|
|
444
|
+
this.pop(!passed);
|
|
445
|
+
if (!passed) {
|
|
446
|
+
if (safe) return false;
|
|
447
|
+
this.throw();
|
|
448
|
+
}
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
validateSafe(def, value) {
|
|
452
|
+
if (this.isLimitExceeded()) return false;
|
|
453
|
+
if (!isAnnotatedType(def)) throw new Error("Can not validate not-annotated type");
|
|
454
|
+
if (typeof this.opts.replace === "function") def = this.opts.replace(def, this.path);
|
|
455
|
+
if (def.optional && value === undefined) return true;
|
|
456
|
+
for (const plugin of this.opts.plugins) {
|
|
457
|
+
const result = plugin(this, def, value);
|
|
458
|
+
if (result === false || result === true) return result;
|
|
459
|
+
}
|
|
460
|
+
return this.validateAnnotatedType(def, value);
|
|
461
|
+
}
|
|
462
|
+
get path() {
|
|
463
|
+
return this.stackPath.slice(1).join(".");
|
|
464
|
+
}
|
|
465
|
+
validateAnnotatedType(def, value) {
|
|
466
|
+
switch (def.type.kind) {
|
|
467
|
+
case "object": return this.validateObject(def, value);
|
|
468
|
+
case "union": return this.validateUnion(def, value);
|
|
469
|
+
case "intersection": return this.validateIntersection(def, value);
|
|
470
|
+
case "tuple": return this.validateTuple(def, value);
|
|
471
|
+
case "array": return this.validateArray(def, value);
|
|
472
|
+
case "": return this.validatePrimitive(def, value);
|
|
473
|
+
default: throw new Error(`Unknown type "${def.type.kind}"`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
validateUnion(def, value) {
|
|
477
|
+
let i = 0;
|
|
478
|
+
const popped = [];
|
|
479
|
+
for (const item of def.type.items) {
|
|
480
|
+
this.push(`[${item.type.kind || item.type.designType}(${i})]`);
|
|
481
|
+
if (this.validateSafe(item, value)) {
|
|
482
|
+
this.pop(false);
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
const errors = this.pop(false);
|
|
486
|
+
if (errors) popped.push(...errors);
|
|
487
|
+
i++;
|
|
488
|
+
}
|
|
489
|
+
this.clear();
|
|
490
|
+
const expected = def.type.items.map((item, i$1) => `[${item.type.kind || item.type.designType}(${i$1})]`).join(", ");
|
|
491
|
+
this.error(`Value does not match any of the allowed types: ${expected}`, undefined, popped);
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
validateIntersection(def, value) {
|
|
495
|
+
for (const item of def.type.items) if (!this.validateSafe(item, value)) return false;
|
|
496
|
+
return true;
|
|
497
|
+
}
|
|
498
|
+
validateTuple(def, value) {
|
|
499
|
+
if (!Array.isArray(value) || value.length !== def.type.items.length) {
|
|
500
|
+
this.error("Expected array of length " + def.type.items.length);
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
let i = 0;
|
|
504
|
+
for (const item of def.type.items) {
|
|
505
|
+
this.push(`[${i}]`);
|
|
506
|
+
if (!this.validateSafe(item, value[i])) {
|
|
507
|
+
this.pop(true);
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
this.pop(false);
|
|
511
|
+
i++;
|
|
512
|
+
}
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
validateArray(def, value) {
|
|
516
|
+
if (!Array.isArray(value)) {
|
|
517
|
+
this.error("Expected array");
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
const minLength = def.metadata.get("expect.minLength");
|
|
521
|
+
if (typeof minLength === "number" && value.length < minLength) {
|
|
522
|
+
this.error(`Expected minimum length of ${minLength} items, got ${value.length} items`);
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
const maxLength = def.metadata.get("expect.maxLength");
|
|
526
|
+
if (typeof maxLength === "number" && value.length > maxLength) {
|
|
527
|
+
this.error(`Expected maximum length of ${maxLength} items, got ${value.length} items`);
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
let i = 0;
|
|
531
|
+
let passed = true;
|
|
532
|
+
for (const item of value) {
|
|
533
|
+
this.push(`[${i}]`);
|
|
534
|
+
if (!this.validateSafe(def.type.of, item)) {
|
|
535
|
+
passed = false;
|
|
536
|
+
this.pop(true);
|
|
537
|
+
if (this.isLimitExceeded()) return false;
|
|
538
|
+
} else this.pop(false);
|
|
539
|
+
i++;
|
|
540
|
+
}
|
|
541
|
+
return passed;
|
|
542
|
+
}
|
|
543
|
+
validateObject(def, value) {
|
|
544
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
545
|
+
this.error("Expected object");
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
let passed = true;
|
|
549
|
+
const valueKeys = new Set(Object.keys(value));
|
|
550
|
+
const typeKeys = new Set();
|
|
551
|
+
const skipList = new Set();
|
|
552
|
+
if (this.opts.skipList) {
|
|
553
|
+
const path$3 = this.stackPath.length > 1 ? this.path + "." : "";
|
|
554
|
+
this.opts.skipList.forEach((item) => {
|
|
555
|
+
if (item.startsWith(path$3)) {
|
|
556
|
+
const key = item.slice(path$3.length);
|
|
557
|
+
skipList.add(key);
|
|
558
|
+
valueKeys.delete(key);
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
let partialFunctionMatched = false;
|
|
563
|
+
if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.path);
|
|
564
|
+
for (const [key, item] of def.type.props.entries()) {
|
|
565
|
+
if (skipList.has(key)) continue;
|
|
566
|
+
typeKeys.add(key);
|
|
567
|
+
if (value[key] === undefined) {
|
|
568
|
+
if (partialFunctionMatched || this.opts.partial === "deep" || this.opts.partial === true && this.stackPath.length <= 1) continue;
|
|
569
|
+
}
|
|
570
|
+
this.push(key);
|
|
571
|
+
if (this.validateSafe(item, value[key])) this.pop(false);
|
|
572
|
+
else {
|
|
573
|
+
passed = false;
|
|
574
|
+
this.pop(true);
|
|
575
|
+
if (this.isLimitExceeded()) return false;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
for (const key of valueKeys)
|
|
579
|
+
/** matched patterns for unknown keys */ if (!typeKeys.has(key)) {
|
|
580
|
+
const matched = [];
|
|
581
|
+
for (const { pattern, def: propDef } of def.type.propsPatterns) if (pattern.test(key)) matched.push({
|
|
582
|
+
pattern,
|
|
583
|
+
def: propDef
|
|
584
|
+
});
|
|
585
|
+
if (matched.length) {
|
|
586
|
+
let keyPassed = false;
|
|
587
|
+
for (const { def: def$1 } of matched) if (this.validateSafe(def$1, value[key])) {
|
|
588
|
+
this.pop(false);
|
|
589
|
+
keyPassed = true;
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
592
|
+
if (!keyPassed) {
|
|
593
|
+
this.push(key);
|
|
594
|
+
this.validateSafe(matched[0].def, value[key]);
|
|
595
|
+
this.pop(true);
|
|
596
|
+
passed = false;
|
|
597
|
+
if (this.isLimitExceeded()) return false;
|
|
598
|
+
}
|
|
599
|
+
} else if (this.opts.unknwonProps !== "ignore") {
|
|
600
|
+
if (this.opts.unknwonProps === "error") {
|
|
601
|
+
this.push(key);
|
|
602
|
+
this.error(`Unexpected property`);
|
|
603
|
+
this.pop(true);
|
|
604
|
+
if (this.isLimitExceeded()) return false;
|
|
605
|
+
passed = false;
|
|
606
|
+
} else if (this.opts.unknwonProps === "strip") delete value[key];
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return passed;
|
|
610
|
+
}
|
|
611
|
+
validatePrimitive(def, value) {
|
|
612
|
+
if (typeof def.type.value !== "undefined") {
|
|
613
|
+
if (value !== def.type.value) {
|
|
614
|
+
this.error(`Expected ${def.type.value}, got ${value}`);
|
|
615
|
+
return false;
|
|
616
|
+
}
|
|
617
|
+
return true;
|
|
618
|
+
}
|
|
619
|
+
const typeOfValue = Array.isArray(value) ? "array" : typeof value;
|
|
620
|
+
switch (def.type.designType) {
|
|
621
|
+
case "never":
|
|
622
|
+
this.error(`This type is impossible, must be an internal problem`);
|
|
623
|
+
return false;
|
|
624
|
+
case "any": return true;
|
|
625
|
+
case "string":
|
|
626
|
+
if (typeOfValue !== def.type.designType) {
|
|
627
|
+
this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
|
|
628
|
+
return false;
|
|
629
|
+
}
|
|
630
|
+
return this.validateString(def, value);
|
|
631
|
+
case "number":
|
|
632
|
+
if (typeOfValue !== def.type.designType) {
|
|
633
|
+
this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
|
|
634
|
+
return false;
|
|
635
|
+
}
|
|
636
|
+
return this.validateNumber(def, value);
|
|
637
|
+
case "boolean":
|
|
638
|
+
if (typeOfValue !== def.type.designType) {
|
|
639
|
+
this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
return true;
|
|
643
|
+
case "undefined":
|
|
644
|
+
if (value !== undefined) {
|
|
645
|
+
this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
return true;
|
|
649
|
+
case "null":
|
|
650
|
+
if (value !== null) {
|
|
651
|
+
this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
return true;
|
|
655
|
+
default: throw new Error(`Unknown type "${def.type.designType}"`);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
validateString(def, value) {
|
|
659
|
+
const minLength = def.metadata.get("expect.minLength");
|
|
660
|
+
if (typeof minLength === "number" && value.length < minLength) {
|
|
661
|
+
this.error(`Expected minimum length of ${minLength} characters, got ${value.length} characters`);
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
const maxLength = def.metadata.get("expect.maxLength");
|
|
665
|
+
if (typeof maxLength === "number" && value.length > maxLength) {
|
|
666
|
+
this.error(`Expected maximum length of ${maxLength} characters, got ${value.length} characters`);
|
|
667
|
+
return false;
|
|
668
|
+
}
|
|
669
|
+
const patterns = def.metadata.get("expect.pattern");
|
|
670
|
+
for (const { pattern, flags, message } of patterns || []) {
|
|
671
|
+
if (!pattern) continue;
|
|
672
|
+
const cacheKey = `${pattern}//${flags || ""}`;
|
|
673
|
+
let regex = regexCache.get(cacheKey);
|
|
674
|
+
if (!regex) {
|
|
675
|
+
regex = new RegExp(pattern, flags);
|
|
676
|
+
regexCache.set(cacheKey, regex);
|
|
677
|
+
}
|
|
678
|
+
if (!regex.test(value)) {
|
|
679
|
+
this.error(message || `Value is expected to match pattern "${pattern}"`);
|
|
680
|
+
return false;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return true;
|
|
684
|
+
}
|
|
685
|
+
validateNumber(def, value) {
|
|
686
|
+
const int = def.metadata.get("expect.int");
|
|
687
|
+
if (typeof int === "boolean" && int && value % 1 !== 0) {
|
|
688
|
+
this.error(`Expected integer, got ${value}`);
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
const min = def.metadata.get("expect.min");
|
|
692
|
+
if (typeof min === "number" && value < min) {
|
|
693
|
+
this.error(`Expected minimum ${min}, got ${value}`);
|
|
694
|
+
return false;
|
|
695
|
+
}
|
|
696
|
+
const max = def.metadata.get("expect.max");
|
|
697
|
+
if (typeof max === "number" && value > max) {
|
|
698
|
+
this.error(`Expected maximum ${max}, got ${value}`);
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
return true;
|
|
702
|
+
}
|
|
703
|
+
constructor(def, opts) {
|
|
704
|
+
_define_property$2(this, "def", void 0);
|
|
705
|
+
_define_property$2(this, "opts", void 0);
|
|
706
|
+
_define_property$2(this, "errors", void 0);
|
|
707
|
+
_define_property$2(this, "stackErrors", void 0);
|
|
708
|
+
_define_property$2(this, "stackPath", void 0);
|
|
709
|
+
this.def = def;
|
|
710
|
+
this.errors = [];
|
|
711
|
+
this.stackErrors = [];
|
|
712
|
+
this.stackPath = [];
|
|
713
|
+
this.opts = {
|
|
714
|
+
partial: false,
|
|
715
|
+
unknwonProps: "error",
|
|
716
|
+
errorLimit: 10,
|
|
717
|
+
...opts,
|
|
718
|
+
plugins: opts?.plugins || []
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
var ValidatorError = class extends Error {
|
|
723
|
+
constructor(errors) {
|
|
724
|
+
super(`${errors[0].path ? errors[0].path + ": " : ""}${errors[0].message}`), _define_property$2(this, "errors", void 0), _define_property$2(this, "name", void 0), this.errors = errors, this.name = "Validation Error";
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
//#endregion
|
|
729
|
+
//#region packages/typescript/src/annotated-type.ts
|
|
730
|
+
function isAnnotatedType(type) {
|
|
731
|
+
return type && type.__is_atscript_annotated_type;
|
|
732
|
+
}
|
|
733
|
+
function defineAnnotatedType(_kind, base) {
|
|
734
|
+
const kind = _kind || "";
|
|
735
|
+
const type = base?.type || {};
|
|
736
|
+
type.kind = kind;
|
|
737
|
+
if ([
|
|
738
|
+
"union",
|
|
739
|
+
"intersection",
|
|
740
|
+
"tuple"
|
|
741
|
+
].includes(kind)) type.items = [];
|
|
742
|
+
if (kind === "object") {
|
|
743
|
+
type.props = new Map();
|
|
744
|
+
type.propsPatterns = [];
|
|
745
|
+
}
|
|
746
|
+
type.tags = new Set();
|
|
747
|
+
const metadata = base?.metadata || new Map();
|
|
748
|
+
if (base) Object.assign(base, {
|
|
749
|
+
__is_atscript_annotated_type: true,
|
|
750
|
+
metadata,
|
|
751
|
+
type,
|
|
752
|
+
validator(opts) {
|
|
753
|
+
return new Validator(this, opts);
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
else base = {
|
|
757
|
+
__is_atscript_annotated_type: true,
|
|
758
|
+
metadata,
|
|
759
|
+
type,
|
|
760
|
+
validator(opts) {
|
|
761
|
+
return new Validator(this, opts);
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
const handle = {
|
|
765
|
+
$type: base,
|
|
766
|
+
$def: type,
|
|
767
|
+
$metadata: metadata,
|
|
768
|
+
_existingObject: undefined,
|
|
769
|
+
tags(...tags) {
|
|
770
|
+
for (const tag of tags) this.$def.tags.add(tag);
|
|
771
|
+
return this;
|
|
772
|
+
},
|
|
773
|
+
designType(value) {
|
|
774
|
+
this.$def.designType = value;
|
|
775
|
+
return this;
|
|
776
|
+
},
|
|
777
|
+
value(value) {
|
|
778
|
+
this.$def.value = value;
|
|
779
|
+
return this;
|
|
780
|
+
},
|
|
781
|
+
of(value) {
|
|
782
|
+
this.$def.of = value;
|
|
783
|
+
return this;
|
|
784
|
+
},
|
|
785
|
+
item(value) {
|
|
786
|
+
this.$def.items.push(value);
|
|
787
|
+
return this;
|
|
788
|
+
},
|
|
789
|
+
prop(name, value) {
|
|
790
|
+
this.$def.props.set(name, value);
|
|
791
|
+
return this;
|
|
792
|
+
},
|
|
793
|
+
propPattern(pattern, def) {
|
|
794
|
+
this.$def.propsPatterns.push({
|
|
795
|
+
pattern,
|
|
796
|
+
def
|
|
797
|
+
});
|
|
798
|
+
return this;
|
|
799
|
+
},
|
|
800
|
+
optional(value = true) {
|
|
801
|
+
this.$type.optional = value;
|
|
802
|
+
return this;
|
|
803
|
+
},
|
|
804
|
+
copyMetadata(fromMetadata, ignore) {
|
|
805
|
+
for (const [key, value] of fromMetadata.entries()) if (!ignore || !ignore.has(key)) this.$metadata.set(key, value);
|
|
806
|
+
return this;
|
|
807
|
+
},
|
|
808
|
+
refTo(type$1, chain) {
|
|
809
|
+
let newBase = type$1;
|
|
810
|
+
const typeName = type$1.name || "Unknown";
|
|
811
|
+
if (isAnnotatedType(newBase)) {
|
|
812
|
+
let keys = "";
|
|
813
|
+
for (const c of chain || []) {
|
|
814
|
+
keys += `["${c}"]`;
|
|
815
|
+
if (newBase.type.kind === "object" && newBase.type.props.has(c)) newBase = newBase.type.props.get(c);
|
|
816
|
+
else throw new Error(`Can't find prop ${typeName}${keys}`);
|
|
817
|
+
}
|
|
818
|
+
if (!newBase && keys) throw new Error(`Can't find prop ${typeName}${keys}`);
|
|
819
|
+
else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
|
|
820
|
+
this.$type = {
|
|
821
|
+
__is_atscript_annotated_type: true,
|
|
822
|
+
type: newBase.type,
|
|
823
|
+
metadata,
|
|
824
|
+
validator(opts) {
|
|
825
|
+
return new Validator(this, opts);
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
} else throw new Error(`${type$1} is not annotated type`);
|
|
829
|
+
return this;
|
|
830
|
+
},
|
|
831
|
+
annotate(key, value, asArray) {
|
|
832
|
+
if (asArray) if (this.$metadata.has(key)) {
|
|
833
|
+
const a = this.$metadata.get(key);
|
|
834
|
+
if (Array.isArray(a)) a.push(value);
|
|
835
|
+
else this.$metadata.set(key, [a, value]);
|
|
836
|
+
} else this.$metadata.set(key, [value]);
|
|
837
|
+
else this.$metadata.set(key, value);
|
|
838
|
+
return this;
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
return handle;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
//#endregion
|
|
845
|
+
//#region packages/typescript/src/json-schema.ts
|
|
846
|
+
function buildJsonSchema(type) {
|
|
847
|
+
const build$1 = (def) => {
|
|
848
|
+
const t = def.type;
|
|
849
|
+
const meta = def.metadata;
|
|
850
|
+
switch (t.kind) {
|
|
851
|
+
case "object": {
|
|
852
|
+
const obj = t;
|
|
853
|
+
const properties = {};
|
|
854
|
+
const required = [];
|
|
855
|
+
for (const [key, val] of obj.props.entries()) {
|
|
856
|
+
properties[key] = build$1(val);
|
|
857
|
+
if (!val.optional) required.push(key);
|
|
858
|
+
}
|
|
859
|
+
const schema = {
|
|
860
|
+
type: "object",
|
|
861
|
+
properties
|
|
862
|
+
};
|
|
863
|
+
if (required.length) schema.required = required;
|
|
864
|
+
return schema;
|
|
865
|
+
}
|
|
866
|
+
case "array": {
|
|
867
|
+
const arr = t;
|
|
868
|
+
const schema = {
|
|
869
|
+
type: "array",
|
|
870
|
+
items: build$1(arr.of)
|
|
871
|
+
};
|
|
872
|
+
const minLength = meta.get("expect.minLength");
|
|
873
|
+
if (typeof minLength === "number") schema.minItems = minLength;
|
|
874
|
+
const maxLength = meta.get("expect.maxLength");
|
|
875
|
+
if (typeof maxLength === "number") schema.maxItems = maxLength;
|
|
876
|
+
return schema;
|
|
877
|
+
}
|
|
878
|
+
case "union": {
|
|
879
|
+
const grp = t;
|
|
880
|
+
return { anyOf: grp.items.map(build$1) };
|
|
881
|
+
}
|
|
882
|
+
case "intersection": {
|
|
883
|
+
const grp = t;
|
|
884
|
+
return { allOf: grp.items.map(build$1) };
|
|
885
|
+
}
|
|
886
|
+
case "tuple": {
|
|
887
|
+
const grp = t;
|
|
888
|
+
return {
|
|
889
|
+
type: "array",
|
|
890
|
+
items: grp.items.map(build$1),
|
|
891
|
+
additionalItems: false
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
case "": {
|
|
895
|
+
const fin = t;
|
|
896
|
+
const schema = {};
|
|
897
|
+
if (fin.value !== undefined) schema.const = fin.value;
|
|
898
|
+
if (fin.designType && fin.designType !== "any") {
|
|
899
|
+
schema.type = fin.designType === "undefined" ? "null" : fin.designType;
|
|
900
|
+
if (schema.type === "number" && meta.get("expect.int")) schema.type = "integer";
|
|
901
|
+
}
|
|
902
|
+
if (schema.type === "string") {
|
|
903
|
+
const minLength = meta.get("expect.minLength");
|
|
904
|
+
if (typeof minLength === "number") schema.minLength = minLength;
|
|
905
|
+
const maxLength = meta.get("expect.maxLength");
|
|
906
|
+
if (typeof maxLength === "number") schema.maxLength = maxLength;
|
|
907
|
+
const patterns = meta.get("expect.pattern");
|
|
908
|
+
if (patterns?.length) if (patterns.length === 1) schema.pattern = patterns[0].pattern;
|
|
909
|
+
else schema.allOf = (schema.allOf || []).concat(patterns.map((p) => ({ pattern: p.pattern })));
|
|
910
|
+
}
|
|
911
|
+
if (schema.type === "number" || schema.type === "integer") {
|
|
912
|
+
const min = meta.get("expect.min");
|
|
913
|
+
if (typeof min === "number") schema.minimum = min;
|
|
914
|
+
const max = meta.get("expect.max");
|
|
915
|
+
if (typeof max === "number") schema.maximum = max;
|
|
916
|
+
}
|
|
917
|
+
return schema;
|
|
918
|
+
}
|
|
919
|
+
default: return {};
|
|
920
|
+
}
|
|
921
|
+
};
|
|
922
|
+
return build$1(type);
|
|
923
|
+
}
|
|
924
|
+
|
|
379
925
|
//#endregion
|
|
380
926
|
//#region packages/typescript/src/codegen/js-renderer.ts
|
|
381
927
|
function _define_property$1(obj, key, value) {
|
|
@@ -392,7 +938,9 @@ var JsRenderer = class extends BaseRenderer {
|
|
|
392
938
|
pre() {
|
|
393
939
|
this.writeln("// prettier-ignore-start");
|
|
394
940
|
this.writeln("/* eslint-disable */");
|
|
395
|
-
|
|
941
|
+
const imports = ["defineAnnotatedType as $"];
|
|
942
|
+
if (!this.opts?.preRenderJsonSchema) imports.push("buildJsonSchema as $$");
|
|
943
|
+
this.writeln(`import { ${imports.join(", ")} } from "@atscript/typescript"`);
|
|
396
944
|
}
|
|
397
945
|
post() {
|
|
398
946
|
for (const node of this.postAnnotate) {
|
|
@@ -412,6 +960,18 @@ var JsRenderer = class extends BaseRenderer {
|
|
|
412
960
|
this.writeln("static __is_atscript_annotated_type = true");
|
|
413
961
|
this.writeln("static type = {}");
|
|
414
962
|
this.writeln("static metadata = new Map()");
|
|
963
|
+
if (this.opts?.preRenderJsonSchema) {
|
|
964
|
+
const schema = JSON.stringify(buildJsonSchema(this.toAnnotatedType(node)));
|
|
965
|
+
this.writeln(`static _jsonSchema = ${schema}`);
|
|
966
|
+
this.writeln("static toJsonSchema() {");
|
|
967
|
+
this.indent().writeln("return this._jsonSchema").unindent();
|
|
968
|
+
this.writeln("}");
|
|
969
|
+
} else {
|
|
970
|
+
this.writeln("static _jsonSchema");
|
|
971
|
+
this.writeln("static toJsonSchema() {");
|
|
972
|
+
this.indent().writeln("return this._jsonSchema ?? (this._jsonSchema = $$(this))").unindent();
|
|
973
|
+
this.writeln("}");
|
|
974
|
+
}
|
|
415
975
|
this.popln();
|
|
416
976
|
this.postAnnotate.push(node);
|
|
417
977
|
this.writeln();
|
|
@@ -425,10 +985,125 @@ var JsRenderer = class extends BaseRenderer {
|
|
|
425
985
|
this.writeln("static __is_atscript_annotated_type = true");
|
|
426
986
|
this.writeln("static type = {}");
|
|
427
987
|
this.writeln("static metadata = new Map()");
|
|
988
|
+
if (this.opts?.jsonSchema) if (typeof this.opts.jsonSchema === "object" && this.opts.jsonSchema.preRender) {
|
|
989
|
+
const schema = JSON.stringify(buildJsonSchema(this.toAnnotatedType(node)));
|
|
990
|
+
this.writeln(`static _jsonSchema = ${schema}`);
|
|
991
|
+
this.writeln("static toJsonSchema() {");
|
|
992
|
+
this.indent().writeln("return this._jsonSchema").unindent();
|
|
993
|
+
this.writeln("}");
|
|
994
|
+
} else {
|
|
995
|
+
this.writeln("static _jsonSchema");
|
|
996
|
+
this.writeln("static toJsonSchema() {");
|
|
997
|
+
this.indent().writeln("return this._jsonSchema ?? (this._jsonSchema = $$(this))").unindent();
|
|
998
|
+
this.writeln("}");
|
|
999
|
+
}
|
|
428
1000
|
this.popln();
|
|
429
1001
|
this.postAnnotate.push(node);
|
|
430
1002
|
this.writeln();
|
|
431
1003
|
}
|
|
1004
|
+
toAnnotatedType(node) {
|
|
1005
|
+
return this.toAnnotatedHandle(node).$type;
|
|
1006
|
+
}
|
|
1007
|
+
toAnnotatedHandle(node, skipAnnotations = false) {
|
|
1008
|
+
if (!node) return defineAnnotatedType();
|
|
1009
|
+
switch (node.entity) {
|
|
1010
|
+
case "interface":
|
|
1011
|
+
case "type": {
|
|
1012
|
+
const def = node.getDefinition();
|
|
1013
|
+
const handle = this.toAnnotatedHandle(def, true);
|
|
1014
|
+
return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
1015
|
+
}
|
|
1016
|
+
case "prop": {
|
|
1017
|
+
const prop = node;
|
|
1018
|
+
const def = prop.getDefinition();
|
|
1019
|
+
const handle = this.toAnnotatedHandle(def, true);
|
|
1020
|
+
if (!skipAnnotations) {
|
|
1021
|
+
this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(prop));
|
|
1022
|
+
if (prop.token("optional")) handle.optional();
|
|
1023
|
+
}
|
|
1024
|
+
return handle;
|
|
1025
|
+
}
|
|
1026
|
+
case "ref": {
|
|
1027
|
+
const ref = node;
|
|
1028
|
+
const decl = this.doc.unwindType(ref.id, ref.chain)?.def;
|
|
1029
|
+
const handle = this.toAnnotatedHandle(decl, true);
|
|
1030
|
+
return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
1031
|
+
}
|
|
1032
|
+
case "primitive": {
|
|
1033
|
+
const prim = node;
|
|
1034
|
+
const handle = defineAnnotatedType();
|
|
1035
|
+
handle.designType(prim.id === "never" ? "never" : prim.config.type);
|
|
1036
|
+
if (!skipAnnotations) this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
1037
|
+
return handle;
|
|
1038
|
+
}
|
|
1039
|
+
case "const": {
|
|
1040
|
+
const c = node;
|
|
1041
|
+
const handle = defineAnnotatedType();
|
|
1042
|
+
const t = c.token("identifier")?.type;
|
|
1043
|
+
handle.designType(t === "number" ? "number" : "string");
|
|
1044
|
+
handle.value(t === "number" ? Number(c.id) : c.id);
|
|
1045
|
+
return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
1046
|
+
}
|
|
1047
|
+
case "structure": {
|
|
1048
|
+
const struct = node;
|
|
1049
|
+
const handle = defineAnnotatedType("object");
|
|
1050
|
+
for (const prop of Array.from(struct.props.values())) {
|
|
1051
|
+
const propHandle = this.toAnnotatedHandle(prop);
|
|
1052
|
+
const pattern = prop.token("identifier")?.pattern;
|
|
1053
|
+
if (pattern) handle.propPattern(pattern, propHandle.$type);
|
|
1054
|
+
else handle.prop(prop.id, propHandle.$type);
|
|
1055
|
+
}
|
|
1056
|
+
return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
1057
|
+
}
|
|
1058
|
+
case "group": {
|
|
1059
|
+
const group = node;
|
|
1060
|
+
const kind = group.op === "|" ? "union" : "intersection";
|
|
1061
|
+
const handle = defineAnnotatedType(kind);
|
|
1062
|
+
for (const item of group.unwrap()) handle.item(this.toAnnotatedHandle(item).$type);
|
|
1063
|
+
return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
1064
|
+
}
|
|
1065
|
+
case "tuple": {
|
|
1066
|
+
const group = node;
|
|
1067
|
+
const handle = defineAnnotatedType("tuple");
|
|
1068
|
+
for (const item of group.unwrap()) handle.item(this.toAnnotatedHandle(item).$type);
|
|
1069
|
+
return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
1070
|
+
}
|
|
1071
|
+
case "array": {
|
|
1072
|
+
const arr = node;
|
|
1073
|
+
const handle = defineAnnotatedType("array");
|
|
1074
|
+
handle.of(this.toAnnotatedHandle(arr.getDefinition()).$type);
|
|
1075
|
+
return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
1076
|
+
}
|
|
1077
|
+
default: {
|
|
1078
|
+
const handle = defineAnnotatedType();
|
|
1079
|
+
return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
applyExpectAnnotations(handle, annotations) {
|
|
1084
|
+
annotations?.forEach((a) => {
|
|
1085
|
+
switch (a.name) {
|
|
1086
|
+
case "expect.minLength":
|
|
1087
|
+
case "expect.maxLength":
|
|
1088
|
+
case "expect.min":
|
|
1089
|
+
case "expect.max":
|
|
1090
|
+
if (a.args[0]) handle.annotate(a.name, Number(a.args[0].text));
|
|
1091
|
+
break;
|
|
1092
|
+
case "expect.pattern":
|
|
1093
|
+
handle.annotate(a.name, {
|
|
1094
|
+
pattern: a.args[0]?.text || "",
|
|
1095
|
+
flags: a.args[1]?.text,
|
|
1096
|
+
message: a.args[2]?.text
|
|
1097
|
+
}, true);
|
|
1098
|
+
break;
|
|
1099
|
+
case "expect.int":
|
|
1100
|
+
handle.annotate(a.name, true);
|
|
1101
|
+
break;
|
|
1102
|
+
default:
|
|
1103
|
+
}
|
|
1104
|
+
});
|
|
1105
|
+
return handle;
|
|
1106
|
+
}
|
|
432
1107
|
annotateType(_node, name) {
|
|
433
1108
|
if (!_node) return this;
|
|
434
1109
|
const node = this.doc.mergeIntersection(_node);
|
|
@@ -479,7 +1154,6 @@ var JsRenderer = class extends BaseRenderer {
|
|
|
479
1154
|
defineConst(node) {
|
|
480
1155
|
const t = node.token("identifier")?.type;
|
|
481
1156
|
const designType = t === "text" ? "string" : t === "number" ? "number" : "unknown";
|
|
482
|
-
const type = t === "text" ? "String" : t === "number" ? "Number" : "undefined";
|
|
483
1157
|
this.writeln(`.designType("${escapeQuotes(designType)}")`);
|
|
484
1158
|
this.writeln(`.value(${t === "text" ? `"${escapeQuotes(node.id)}"` : node.id})`);
|
|
485
1159
|
return this;
|
|
@@ -619,24 +1293,24 @@ else targetValue = "true";
|
|
|
619
1293
|
if (multiple) this.writeln(`.annotate("${escapeQuotes(an.name)}", ${targetValue}, true)`);
|
|
620
1294
|
else this.writeln(`.annotate("${escapeQuotes(an.name)}", ${targetValue})`);
|
|
621
1295
|
}
|
|
622
|
-
constructor(
|
|
623
|
-
super(
|
|
1296
|
+
constructor(doc, opts) {
|
|
1297
|
+
super(doc), _define_property$1(this, "opts", void 0), _define_property$1(this, "postAnnotate", void 0), this.opts = opts, this.postAnnotate = [];
|
|
624
1298
|
}
|
|
625
1299
|
};
|
|
626
1300
|
|
|
627
1301
|
//#endregion
|
|
628
1302
|
//#region packages/typescript/src/plugin.ts
|
|
629
|
-
const tsPlugin = () => {
|
|
1303
|
+
const tsPlugin = (opts) => {
|
|
630
1304
|
return {
|
|
631
1305
|
name: "typesccript",
|
|
632
1306
|
render(doc, format) {
|
|
633
1307
|
if (format === "dts") return [{
|
|
634
1308
|
fileName: `${doc.name}.d.ts`,
|
|
635
|
-
content: new TypeRenderer(doc).render()
|
|
1309
|
+
content: new TypeRenderer(doc, opts).render()
|
|
636
1310
|
}];
|
|
637
1311
|
if (format === "js") return [{
|
|
638
1312
|
fileName: `${doc.name}.js`,
|
|
639
|
-
content: new JsRenderer(doc).render()
|
|
1313
|
+
content: new JsRenderer(doc, opts).render()
|
|
640
1314
|
}];
|
|
641
1315
|
},
|
|
642
1316
|
async buildEnd(output, format, repo) {
|