@formspec/build 0.1.0-alpha.16 → 0.1.0-alpha.19
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 +74 -128
- package/dist/__tests__/class-schema.test.d.ts +2 -0
- package/dist/__tests__/class-schema.test.d.ts.map +1 -0
- package/dist/__tests__/date-extension.integration.test.d.ts +2 -0
- package/dist/__tests__/date-extension.integration.test.d.ts.map +1 -0
- package/dist/__tests__/fixtures/class-schema-regressions.d.ts +83 -0
- package/dist/__tests__/fixtures/class-schema-regressions.d.ts.map +1 -0
- package/dist/__tests__/fixtures/example-date-extension.d.ts +12 -0
- package/dist/__tests__/fixtures/example-date-extension.d.ts.map +1 -0
- package/dist/__tests__/fixtures/example-numeric-extension.d.ts +20 -0
- package/dist/__tests__/fixtures/example-numeric-extension.d.ts.map +1 -0
- package/dist/__tests__/fixtures/extension-forms.d.ts +7 -0
- package/dist/__tests__/fixtures/extension-forms.d.ts.map +1 -0
- package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts +1 -0
- package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts.map +1 -1
- package/dist/__tests__/fixtures/named-primitive-aliases.d.ts +15 -0
- package/dist/__tests__/fixtures/named-primitive-aliases.d.ts.map +1 -0
- package/dist/__tests__/fixtures/nested-array-path-constraints.d.ts +14 -0
- package/dist/__tests__/fixtures/nested-array-path-constraints.d.ts.map +1 -0
- package/dist/__tests__/fixtures/sample-forms.d.ts +10 -0
- package/dist/__tests__/fixtures/sample-forms.d.ts.map +1 -1
- package/dist/__tests__/generate-schemas.test.d.ts +2 -0
- package/dist/__tests__/generate-schemas.test.d.ts.map +1 -0
- package/dist/__tests__/numeric-extension.integration.test.d.ts +2 -0
- package/dist/__tests__/numeric-extension.integration.test.d.ts.map +1 -0
- package/dist/__tests__/parity/parity.test.d.ts +6 -2
- package/dist/__tests__/parity/parity.test.d.ts.map +1 -1
- package/dist/__tests__/parity/utils.d.ts +9 -4
- package/dist/__tests__/parity/utils.d.ts.map +1 -1
- package/dist/analyzer/class-analyzer.d.ts +5 -4
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/analyzer/jsdoc-constraints.d.ts +3 -2
- package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
- package/dist/analyzer/program.d.ts +15 -0
- package/dist/analyzer/program.d.ts.map +1 -1
- package/dist/analyzer/tsdoc-parser.d.ts +23 -2
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
- package/dist/browser.cjs +269 -11
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.js +269 -11
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +28 -2
- package/dist/canonicalize/chain-dsl-canonicalizer.d.ts.map +1 -1
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
- package/dist/cli.cjs +1640 -282
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1638 -281
- package/dist/cli.js.map +1 -1
- package/dist/extensions/registry.d.ts +25 -1
- package/dist/extensions/registry.d.ts.map +1 -1
- package/dist/generators/class-schema.d.ts +4 -4
- package/dist/generators/class-schema.d.ts.map +1 -1
- package/dist/generators/method-schema.d.ts.map +1 -1
- package/dist/generators/mixed-authoring.d.ts.map +1 -1
- package/dist/index.cjs +1615 -271
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1615 -271
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +990 -236
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +988 -234
- package/dist/internals.js.map +1 -1
- package/dist/json-schema/ir-generator.d.ts.map +1 -1
- package/dist/validate/constraint-validator.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/internals.js
CHANGED
|
@@ -20,6 +20,7 @@ function canonicalizeChainDSL(form) {
|
|
|
20
20
|
kind: "form-ir",
|
|
21
21
|
irVersion: IR_VERSION,
|
|
22
22
|
elements: canonicalizeElements(form.elements),
|
|
23
|
+
rootAnnotations: [],
|
|
23
24
|
typeRegistry: {},
|
|
24
25
|
provenance: CHAIN_DSL_PROVENANCE
|
|
25
26
|
};
|
|
@@ -325,6 +326,7 @@ function canonicalizeTSDoc(analysis, source) {
|
|
|
325
326
|
irVersion: IR_VERSION2,
|
|
326
327
|
elements,
|
|
327
328
|
typeRegistry: analysis.typeRegistry,
|
|
329
|
+
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { rootAnnotations: analysis.annotations },
|
|
328
330
|
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
|
|
329
331
|
provenance
|
|
330
332
|
};
|
|
@@ -385,85 +387,17 @@ function wrapInConditional(field, layout, provenance) {
|
|
|
385
387
|
}
|
|
386
388
|
|
|
387
389
|
// src/analyzer/program.ts
|
|
388
|
-
import * as
|
|
390
|
+
import * as ts4 from "typescript";
|
|
389
391
|
import * as path from "path";
|
|
390
|
-
function createProgramContext(filePath) {
|
|
391
|
-
const absolutePath = path.resolve(filePath);
|
|
392
|
-
const fileDir = path.dirname(absolutePath);
|
|
393
|
-
const configPath = ts.findConfigFile(fileDir, ts.sys.fileExists.bind(ts.sys), "tsconfig.json");
|
|
394
|
-
let compilerOptions;
|
|
395
|
-
let fileNames;
|
|
396
|
-
if (configPath) {
|
|
397
|
-
const configFile = ts.readConfigFile(configPath, ts.sys.readFile.bind(ts.sys));
|
|
398
|
-
if (configFile.error) {
|
|
399
|
-
throw new Error(
|
|
400
|
-
`Error reading tsconfig.json: ${ts.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
|
|
401
|
-
);
|
|
402
|
-
}
|
|
403
|
-
const parsed = ts.parseJsonConfigFileContent(
|
|
404
|
-
configFile.config,
|
|
405
|
-
ts.sys,
|
|
406
|
-
path.dirname(configPath)
|
|
407
|
-
);
|
|
408
|
-
if (parsed.errors.length > 0) {
|
|
409
|
-
const errorMessages = parsed.errors.map((e) => ts.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
|
|
410
|
-
throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
|
|
411
|
-
}
|
|
412
|
-
compilerOptions = parsed.options;
|
|
413
|
-
fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
|
|
414
|
-
} else {
|
|
415
|
-
compilerOptions = {
|
|
416
|
-
target: ts.ScriptTarget.ES2022,
|
|
417
|
-
module: ts.ModuleKind.NodeNext,
|
|
418
|
-
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
419
|
-
strict: true,
|
|
420
|
-
skipLibCheck: true,
|
|
421
|
-
declaration: true
|
|
422
|
-
};
|
|
423
|
-
fileNames = [absolutePath];
|
|
424
|
-
}
|
|
425
|
-
const program = ts.createProgram(fileNames, compilerOptions);
|
|
426
|
-
const sourceFile = program.getSourceFile(absolutePath);
|
|
427
|
-
if (!sourceFile) {
|
|
428
|
-
throw new Error(`Could not find source file: ${absolutePath}`);
|
|
429
|
-
}
|
|
430
|
-
return {
|
|
431
|
-
program,
|
|
432
|
-
checker: program.getTypeChecker(),
|
|
433
|
-
sourceFile
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
function findNodeByName(sourceFile, name, predicate, getName) {
|
|
437
|
-
let result = null;
|
|
438
|
-
function visit(node) {
|
|
439
|
-
if (result) return;
|
|
440
|
-
if (predicate(node) && getName(node) === name) {
|
|
441
|
-
result = node;
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
ts.forEachChild(node, visit);
|
|
445
|
-
}
|
|
446
|
-
visit(sourceFile);
|
|
447
|
-
return result;
|
|
448
|
-
}
|
|
449
|
-
function findClassByName(sourceFile, className) {
|
|
450
|
-
return findNodeByName(sourceFile, className, ts.isClassDeclaration, (n) => n.name?.text);
|
|
451
|
-
}
|
|
452
|
-
function findInterfaceByName(sourceFile, interfaceName) {
|
|
453
|
-
return findNodeByName(sourceFile, interfaceName, ts.isInterfaceDeclaration, (n) => n.name.text);
|
|
454
|
-
}
|
|
455
|
-
function findTypeAliasByName(sourceFile, aliasName) {
|
|
456
|
-
return findNodeByName(sourceFile, aliasName, ts.isTypeAliasDeclaration, (n) => n.name.text);
|
|
457
|
-
}
|
|
458
392
|
|
|
459
393
|
// src/analyzer/class-analyzer.ts
|
|
460
|
-
import * as
|
|
394
|
+
import * as ts3 from "typescript";
|
|
461
395
|
|
|
462
396
|
// src/analyzer/jsdoc-constraints.ts
|
|
463
|
-
import * as
|
|
397
|
+
import * as ts2 from "typescript";
|
|
464
398
|
|
|
465
399
|
// src/analyzer/tsdoc-parser.ts
|
|
466
|
-
import * as
|
|
400
|
+
import * as ts from "typescript";
|
|
467
401
|
import {
|
|
468
402
|
TSDocParser,
|
|
469
403
|
TSDocConfiguration,
|
|
@@ -503,7 +437,7 @@ var LENGTH_CONSTRAINT_MAP = {
|
|
|
503
437
|
maxItems: "maxItems"
|
|
504
438
|
};
|
|
505
439
|
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
|
|
506
|
-
function createFormSpecTSDocConfig() {
|
|
440
|
+
function createFormSpecTSDocConfig(extensionTagNames = []) {
|
|
507
441
|
const config = new TSDocConfiguration();
|
|
508
442
|
for (const tagName of Object.keys(BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
509
443
|
config.addTagDefinition(
|
|
@@ -523,14 +457,34 @@ function createFormSpecTSDocConfig() {
|
|
|
523
457
|
})
|
|
524
458
|
);
|
|
525
459
|
}
|
|
460
|
+
for (const tagName of extensionTagNames) {
|
|
461
|
+
config.addTagDefinition(
|
|
462
|
+
new TSDocTagDefinition({
|
|
463
|
+
tagName: "@" + tagName,
|
|
464
|
+
syntaxKind: TSDocTagSyntaxKind.BlockTag,
|
|
465
|
+
allowMultiple: true
|
|
466
|
+
})
|
|
467
|
+
);
|
|
468
|
+
}
|
|
526
469
|
return config;
|
|
527
470
|
}
|
|
528
|
-
var
|
|
529
|
-
function getParser() {
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
471
|
+
var parserCache = /* @__PURE__ */ new Map();
|
|
472
|
+
function getParser(options) {
|
|
473
|
+
const extensionTagNames = [
|
|
474
|
+
...options?.extensionRegistry?.extensions.flatMap(
|
|
475
|
+
(extension) => (extension.constraintTags ?? []).map((tag) => tag.tagName)
|
|
476
|
+
) ?? []
|
|
477
|
+
].sort();
|
|
478
|
+
const cacheKey = extensionTagNames.join("|");
|
|
479
|
+
const existing = parserCache.get(cacheKey);
|
|
480
|
+
if (existing) {
|
|
481
|
+
return existing;
|
|
482
|
+
}
|
|
483
|
+
const parser = new TSDocParser(createFormSpecTSDocConfig(extensionTagNames));
|
|
484
|
+
parserCache.set(cacheKey, parser);
|
|
485
|
+
return parser;
|
|
486
|
+
}
|
|
487
|
+
function parseTSDocTags(node, file = "", options) {
|
|
534
488
|
const constraints = [];
|
|
535
489
|
const annotations = [];
|
|
536
490
|
let displayName;
|
|
@@ -541,17 +495,17 @@ function parseTSDocTags(node, file = "") {
|
|
|
541
495
|
let placeholderProvenance;
|
|
542
496
|
const sourceFile = node.getSourceFile();
|
|
543
497
|
const sourceText = sourceFile.getFullText();
|
|
544
|
-
const commentRanges =
|
|
498
|
+
const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
545
499
|
if (commentRanges) {
|
|
546
500
|
for (const range of commentRanges) {
|
|
547
|
-
if (range.kind !==
|
|
501
|
+
if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
|
|
548
502
|
continue;
|
|
549
503
|
}
|
|
550
504
|
const commentText = sourceText.substring(range.pos, range.end);
|
|
551
505
|
if (!commentText.startsWith("/**")) {
|
|
552
506
|
continue;
|
|
553
507
|
}
|
|
554
|
-
const parser = getParser();
|
|
508
|
+
const parser = getParser(options);
|
|
555
509
|
const parserContext = parser.parseRange(
|
|
556
510
|
TextRange.fromStringRange(sourceText, range.pos, range.end)
|
|
557
511
|
);
|
|
@@ -562,26 +516,31 @@ function parseTSDocTags(node, file = "") {
|
|
|
562
516
|
const text2 = extractBlockText(block).trim();
|
|
563
517
|
if (text2 === "") continue;
|
|
564
518
|
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
displayName
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
519
|
+
switch (tagName) {
|
|
520
|
+
case "displayName":
|
|
521
|
+
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
522
|
+
displayName = text2;
|
|
523
|
+
displayNameProvenance = provenance2;
|
|
524
|
+
}
|
|
525
|
+
break;
|
|
526
|
+
case "format":
|
|
527
|
+
annotations.push({
|
|
528
|
+
kind: "annotation",
|
|
529
|
+
annotationKind: "format",
|
|
530
|
+
value: text2,
|
|
531
|
+
provenance: provenance2
|
|
532
|
+
});
|
|
533
|
+
break;
|
|
534
|
+
case "description":
|
|
579
535
|
description = text2;
|
|
580
536
|
descriptionProvenance = provenance2;
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
537
|
+
break;
|
|
538
|
+
case "placeholder":
|
|
539
|
+
if (placeholder === void 0) {
|
|
540
|
+
placeholder = text2;
|
|
541
|
+
placeholderProvenance = provenance2;
|
|
542
|
+
}
|
|
543
|
+
break;
|
|
585
544
|
}
|
|
586
545
|
continue;
|
|
587
546
|
}
|
|
@@ -590,7 +549,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
590
549
|
const expectedType = isBuiltinConstraintName(tagName) ? BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
|
|
591
550
|
if (text === "" && expectedType !== "boolean") continue;
|
|
592
551
|
const provenance = provenanceForComment(range, sourceFile, file, tagName);
|
|
593
|
-
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
552
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance, options);
|
|
594
553
|
if (constraintNode) {
|
|
595
554
|
constraints.push(constraintNode);
|
|
596
555
|
}
|
|
@@ -611,6 +570,13 @@ function parseTSDocTags(node, file = "") {
|
|
|
611
570
|
descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
|
|
612
571
|
}
|
|
613
572
|
}
|
|
573
|
+
if (description === void 0) {
|
|
574
|
+
const summary = extractPlainText(docComment.summarySection).trim();
|
|
575
|
+
if (summary !== "") {
|
|
576
|
+
description = summary;
|
|
577
|
+
descriptionProvenance = provenanceForComment(range, sourceFile, file, "summary");
|
|
578
|
+
}
|
|
579
|
+
}
|
|
614
580
|
}
|
|
615
581
|
}
|
|
616
582
|
if (displayName !== void 0 && displayNameProvenance !== void 0) {
|
|
@@ -637,7 +603,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
637
603
|
provenance: placeholderProvenance
|
|
638
604
|
});
|
|
639
605
|
}
|
|
640
|
-
const jsDocTagsAll =
|
|
606
|
+
const jsDocTagsAll = ts.getJSDocTags(node);
|
|
641
607
|
for (const tag of jsDocTagsAll) {
|
|
642
608
|
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
643
609
|
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
@@ -650,7 +616,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
650
616
|
annotations.push(defaultValueNode);
|
|
651
617
|
continue;
|
|
652
618
|
}
|
|
653
|
-
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
619
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance, options);
|
|
654
620
|
if (constraintNode) {
|
|
655
621
|
constraints.push(constraintNode);
|
|
656
622
|
}
|
|
@@ -660,7 +626,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
660
626
|
function extractDisplayNameMetadata(node) {
|
|
661
627
|
let displayName;
|
|
662
628
|
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
663
|
-
for (const tag of
|
|
629
|
+
for (const tag of ts.getJSDocTags(node)) {
|
|
664
630
|
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
665
631
|
if (tagName !== "displayName") continue;
|
|
666
632
|
const commentText = getTagCommentText(tag);
|
|
@@ -681,11 +647,11 @@ function extractDisplayNameMetadata(node) {
|
|
|
681
647
|
}
|
|
682
648
|
function extractPathTarget(text) {
|
|
683
649
|
const trimmed = text.trimStart();
|
|
684
|
-
const match = /^:([a-zA-Z_]\w*)
|
|
685
|
-
if (!match?.[1]
|
|
650
|
+
const match = /^:([a-zA-Z_]\w*)(?:\s+([\s\S]*))?$/.exec(trimmed);
|
|
651
|
+
if (!match?.[1]) return null;
|
|
686
652
|
return {
|
|
687
653
|
path: { segments: [match[1]] },
|
|
688
|
-
remainingText: match[2]
|
|
654
|
+
remainingText: match[2] ?? ""
|
|
689
655
|
};
|
|
690
656
|
}
|
|
691
657
|
function extractBlockText(block) {
|
|
@@ -706,7 +672,11 @@ function extractPlainText(node) {
|
|
|
706
672
|
}
|
|
707
673
|
return result;
|
|
708
674
|
}
|
|
709
|
-
function parseConstraintValue(tagName, text, provenance) {
|
|
675
|
+
function parseConstraintValue(tagName, text, provenance, options) {
|
|
676
|
+
const customConstraint = parseExtensionConstraintValue(tagName, text, provenance, options);
|
|
677
|
+
if (customConstraint) {
|
|
678
|
+
return customConstraint;
|
|
679
|
+
}
|
|
710
680
|
if (!isBuiltinConstraintName(tagName)) {
|
|
711
681
|
return null;
|
|
712
682
|
}
|
|
@@ -811,6 +781,83 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
811
781
|
provenance
|
|
812
782
|
};
|
|
813
783
|
}
|
|
784
|
+
function parseExtensionConstraintValue(tagName, text, provenance, options) {
|
|
785
|
+
const pathResult = extractPathTarget(text);
|
|
786
|
+
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
787
|
+
const path2 = pathResult?.path;
|
|
788
|
+
const registry = options?.extensionRegistry;
|
|
789
|
+
if (registry === void 0) {
|
|
790
|
+
return null;
|
|
791
|
+
}
|
|
792
|
+
const directTag = registry.findConstraintTag(tagName);
|
|
793
|
+
if (directTag !== void 0) {
|
|
794
|
+
return makeCustomConstraintNode(
|
|
795
|
+
directTag.extensionId,
|
|
796
|
+
directTag.registration.constraintName,
|
|
797
|
+
directTag.registration.parseValue(effectiveText),
|
|
798
|
+
provenance,
|
|
799
|
+
path2,
|
|
800
|
+
registry
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
if (!isBuiltinConstraintName(tagName)) {
|
|
804
|
+
return null;
|
|
805
|
+
}
|
|
806
|
+
const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
|
|
807
|
+
if (broadenedTypeId === void 0) {
|
|
808
|
+
return null;
|
|
809
|
+
}
|
|
810
|
+
const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
|
|
811
|
+
if (broadened === void 0) {
|
|
812
|
+
return null;
|
|
813
|
+
}
|
|
814
|
+
return makeCustomConstraintNode(
|
|
815
|
+
broadened.extensionId,
|
|
816
|
+
broadened.registration.constraintName,
|
|
817
|
+
broadened.registration.parseValue(effectiveText),
|
|
818
|
+
provenance,
|
|
819
|
+
path2,
|
|
820
|
+
registry
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
function getBroadenedCustomTypeId(fieldType) {
|
|
824
|
+
if (fieldType?.kind === "custom") {
|
|
825
|
+
return fieldType.typeId;
|
|
826
|
+
}
|
|
827
|
+
if (fieldType?.kind !== "union") {
|
|
828
|
+
return void 0;
|
|
829
|
+
}
|
|
830
|
+
const customMembers = fieldType.members.filter(
|
|
831
|
+
(member) => member.kind === "custom"
|
|
832
|
+
);
|
|
833
|
+
if (customMembers.length !== 1) {
|
|
834
|
+
return void 0;
|
|
835
|
+
}
|
|
836
|
+
const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
|
|
837
|
+
const allOtherMembersAreNull = nonCustomMembers.every(
|
|
838
|
+
(member) => member.kind === "primitive" && member.primitiveKind === "null"
|
|
839
|
+
);
|
|
840
|
+
const customMember = customMembers[0];
|
|
841
|
+
return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
|
|
842
|
+
}
|
|
843
|
+
function makeCustomConstraintNode(extensionId, constraintName, payload, provenance, path2, registry) {
|
|
844
|
+
const constraintId = `${extensionId}/${constraintName}`;
|
|
845
|
+
const registration = registry.findConstraint(constraintId);
|
|
846
|
+
if (registration === void 0) {
|
|
847
|
+
throw new Error(
|
|
848
|
+
`Custom TSDoc tag resolved to unregistered constraint "${constraintId}". Register the constraint before using its tag.`
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
return {
|
|
852
|
+
kind: "constraint",
|
|
853
|
+
constraintKind: "custom",
|
|
854
|
+
constraintId,
|
|
855
|
+
payload,
|
|
856
|
+
compositionRule: registration.compositionRule,
|
|
857
|
+
...path2 && { path: path2 },
|
|
858
|
+
provenance
|
|
859
|
+
};
|
|
860
|
+
}
|
|
814
861
|
function parseDefaultValueValue(text, provenance) {
|
|
815
862
|
const trimmed = text.trim();
|
|
816
863
|
let value;
|
|
@@ -867,33 +914,33 @@ function getTagCommentText(tag) {
|
|
|
867
914
|
if (typeof tag.comment === "string") {
|
|
868
915
|
return tag.comment;
|
|
869
916
|
}
|
|
870
|
-
return
|
|
917
|
+
return ts.getTextOfJSDocComment(tag.comment);
|
|
871
918
|
}
|
|
872
919
|
|
|
873
920
|
// src/analyzer/jsdoc-constraints.ts
|
|
874
|
-
function extractJSDocConstraintNodes(node, file = "") {
|
|
875
|
-
const result = parseTSDocTags(node, file);
|
|
921
|
+
function extractJSDocConstraintNodes(node, file = "", options) {
|
|
922
|
+
const result = parseTSDocTags(node, file, options);
|
|
876
923
|
return [...result.constraints];
|
|
877
924
|
}
|
|
878
|
-
function extractJSDocAnnotationNodes(node, file = "") {
|
|
879
|
-
const result = parseTSDocTags(node, file);
|
|
925
|
+
function extractJSDocAnnotationNodes(node, file = "", options) {
|
|
926
|
+
const result = parseTSDocTags(node, file, options);
|
|
880
927
|
return [...result.annotations];
|
|
881
928
|
}
|
|
882
929
|
function extractDefaultValueAnnotation(initializer, file = "") {
|
|
883
930
|
if (!initializer) return null;
|
|
884
931
|
let value;
|
|
885
|
-
if (
|
|
932
|
+
if (ts2.isStringLiteral(initializer)) {
|
|
886
933
|
value = initializer.text;
|
|
887
|
-
} else if (
|
|
934
|
+
} else if (ts2.isNumericLiteral(initializer)) {
|
|
888
935
|
value = Number(initializer.text);
|
|
889
|
-
} else if (initializer.kind ===
|
|
936
|
+
} else if (initializer.kind === ts2.SyntaxKind.TrueKeyword) {
|
|
890
937
|
value = true;
|
|
891
|
-
} else if (initializer.kind ===
|
|
938
|
+
} else if (initializer.kind === ts2.SyntaxKind.FalseKeyword) {
|
|
892
939
|
value = false;
|
|
893
|
-
} else if (initializer.kind ===
|
|
940
|
+
} else if (initializer.kind === ts2.SyntaxKind.NullKeyword) {
|
|
894
941
|
value = null;
|
|
895
|
-
} else if (
|
|
896
|
-
if (initializer.operator ===
|
|
942
|
+
} else if (ts2.isPrefixUnaryExpression(initializer)) {
|
|
943
|
+
if (initializer.operator === ts2.SyntaxKind.MinusToken && ts2.isNumericLiteral(initializer.operand)) {
|
|
897
944
|
value = -Number(initializer.operand.text);
|
|
898
945
|
}
|
|
899
946
|
}
|
|
@@ -915,36 +962,56 @@ function extractDefaultValueAnnotation(initializer, file = "") {
|
|
|
915
962
|
|
|
916
963
|
// src/analyzer/class-analyzer.ts
|
|
917
964
|
function isObjectType(type) {
|
|
918
|
-
return !!(type.flags &
|
|
965
|
+
return !!(type.flags & ts3.TypeFlags.Object);
|
|
919
966
|
}
|
|
920
967
|
function isTypeReference(type) {
|
|
921
|
-
return !!(type.flags &
|
|
968
|
+
return !!(type.flags & ts3.TypeFlags.Object) && !!(type.objectFlags & ts3.ObjectFlags.Reference);
|
|
922
969
|
}
|
|
923
970
|
var RESOLVING_TYPE_PLACEHOLDER = {
|
|
924
971
|
kind: "object",
|
|
925
972
|
properties: [],
|
|
926
973
|
additionalProperties: true
|
|
927
974
|
};
|
|
928
|
-
function
|
|
975
|
+
function makeParseOptions(extensionRegistry, fieldType) {
|
|
976
|
+
if (extensionRegistry === void 0 && fieldType === void 0) {
|
|
977
|
+
return void 0;
|
|
978
|
+
}
|
|
979
|
+
return {
|
|
980
|
+
...extensionRegistry !== void 0 && { extensionRegistry },
|
|
981
|
+
...fieldType !== void 0 && { fieldType }
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
929
985
|
const name = classDecl.name?.text ?? "AnonymousClass";
|
|
930
986
|
const fields = [];
|
|
931
987
|
const fieldLayouts = [];
|
|
932
988
|
const typeRegistry = {};
|
|
933
|
-
const annotations = extractJSDocAnnotationNodes(
|
|
989
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
990
|
+
classDecl,
|
|
991
|
+
file,
|
|
992
|
+
makeParseOptions(extensionRegistry)
|
|
993
|
+
);
|
|
934
994
|
const visiting = /* @__PURE__ */ new Set();
|
|
935
995
|
const instanceMethods = [];
|
|
936
996
|
const staticMethods = [];
|
|
937
997
|
for (const member of classDecl.members) {
|
|
938
|
-
if (
|
|
939
|
-
const fieldNode = analyzeFieldToIR(
|
|
998
|
+
if (ts3.isPropertyDeclaration(member)) {
|
|
999
|
+
const fieldNode = analyzeFieldToIR(
|
|
1000
|
+
member,
|
|
1001
|
+
checker,
|
|
1002
|
+
file,
|
|
1003
|
+
typeRegistry,
|
|
1004
|
+
visiting,
|
|
1005
|
+
extensionRegistry
|
|
1006
|
+
);
|
|
940
1007
|
if (fieldNode) {
|
|
941
1008
|
fields.push(fieldNode);
|
|
942
1009
|
fieldLayouts.push({});
|
|
943
1010
|
}
|
|
944
|
-
} else if (
|
|
1011
|
+
} else if (ts3.isMethodDeclaration(member)) {
|
|
945
1012
|
const methodInfo = analyzeMethod(member, checker);
|
|
946
1013
|
if (methodInfo) {
|
|
947
|
-
const isStatic = member.modifiers?.some((m) => m.kind ===
|
|
1014
|
+
const isStatic = member.modifiers?.some((m) => m.kind === ts3.SyntaxKind.StaticKeyword);
|
|
948
1015
|
if (isStatic) {
|
|
949
1016
|
staticMethods.push(methodInfo);
|
|
950
1017
|
} else {
|
|
@@ -963,15 +1030,26 @@ function analyzeClassToIR(classDecl, checker, file = "") {
|
|
|
963
1030
|
staticMethods
|
|
964
1031
|
};
|
|
965
1032
|
}
|
|
966
|
-
function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
1033
|
+
function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
|
|
967
1034
|
const name = interfaceDecl.name.text;
|
|
968
1035
|
const fields = [];
|
|
969
1036
|
const typeRegistry = {};
|
|
970
|
-
const annotations = extractJSDocAnnotationNodes(
|
|
1037
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1038
|
+
interfaceDecl,
|
|
1039
|
+
file,
|
|
1040
|
+
makeParseOptions(extensionRegistry)
|
|
1041
|
+
);
|
|
971
1042
|
const visiting = /* @__PURE__ */ new Set();
|
|
972
1043
|
for (const member of interfaceDecl.members) {
|
|
973
|
-
if (
|
|
974
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1044
|
+
if (ts3.isPropertySignature(member)) {
|
|
1045
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1046
|
+
member,
|
|
1047
|
+
checker,
|
|
1048
|
+
file,
|
|
1049
|
+
typeRegistry,
|
|
1050
|
+
visiting,
|
|
1051
|
+
extensionRegistry
|
|
1052
|
+
);
|
|
975
1053
|
if (fieldNode) {
|
|
976
1054
|
fields.push(fieldNode);
|
|
977
1055
|
}
|
|
@@ -988,11 +1066,11 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
|
988
1066
|
staticMethods: []
|
|
989
1067
|
};
|
|
990
1068
|
}
|
|
991
|
-
function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
992
|
-
if (!
|
|
1069
|
+
function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
|
|
1070
|
+
if (!ts3.isTypeLiteralNode(typeAlias.type)) {
|
|
993
1071
|
const sourceFile = typeAlias.getSourceFile();
|
|
994
1072
|
const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
|
|
995
|
-
const kindDesc =
|
|
1073
|
+
const kindDesc = ts3.SyntaxKind[typeAlias.type.kind] ?? "unknown";
|
|
996
1074
|
return {
|
|
997
1075
|
ok: false,
|
|
998
1076
|
error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
|
|
@@ -1001,11 +1079,22 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1001
1079
|
const name = typeAlias.name.text;
|
|
1002
1080
|
const fields = [];
|
|
1003
1081
|
const typeRegistry = {};
|
|
1004
|
-
const annotations = extractJSDocAnnotationNodes(
|
|
1082
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1083
|
+
typeAlias,
|
|
1084
|
+
file,
|
|
1085
|
+
makeParseOptions(extensionRegistry)
|
|
1086
|
+
);
|
|
1005
1087
|
const visiting = /* @__PURE__ */ new Set();
|
|
1006
1088
|
for (const member of typeAlias.type.members) {
|
|
1007
|
-
if (
|
|
1008
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1089
|
+
if (ts3.isPropertySignature(member)) {
|
|
1090
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1091
|
+
member,
|
|
1092
|
+
checker,
|
|
1093
|
+
file,
|
|
1094
|
+
typeRegistry,
|
|
1095
|
+
visiting,
|
|
1096
|
+
extensionRegistry
|
|
1097
|
+
);
|
|
1009
1098
|
if (fieldNode) {
|
|
1010
1099
|
fields.push(fieldNode);
|
|
1011
1100
|
}
|
|
@@ -1024,22 +1113,36 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1024
1113
|
}
|
|
1025
1114
|
};
|
|
1026
1115
|
}
|
|
1027
|
-
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
1028
|
-
if (!
|
|
1116
|
+
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1117
|
+
if (!ts3.isIdentifier(prop.name)) {
|
|
1029
1118
|
return null;
|
|
1030
1119
|
}
|
|
1031
1120
|
const name = prop.name.text;
|
|
1032
1121
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1033
1122
|
const optional = prop.questionToken !== void 0;
|
|
1034
1123
|
const provenance = provenanceForNode(prop, file);
|
|
1035
|
-
let type = resolveTypeNode(
|
|
1124
|
+
let type = resolveTypeNode(
|
|
1125
|
+
tsType,
|
|
1126
|
+
checker,
|
|
1127
|
+
file,
|
|
1128
|
+
typeRegistry,
|
|
1129
|
+
visiting,
|
|
1130
|
+
prop,
|
|
1131
|
+
extensionRegistry
|
|
1132
|
+
);
|
|
1036
1133
|
const constraints = [];
|
|
1037
|
-
if (prop.type) {
|
|
1038
|
-
constraints.push(
|
|
1134
|
+
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
1135
|
+
constraints.push(
|
|
1136
|
+
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
1137
|
+
);
|
|
1039
1138
|
}
|
|
1040
|
-
constraints.push(
|
|
1139
|
+
constraints.push(
|
|
1140
|
+
...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
1141
|
+
);
|
|
1041
1142
|
let annotations = [];
|
|
1042
|
-
annotations.push(
|
|
1143
|
+
annotations.push(
|
|
1144
|
+
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
1145
|
+
);
|
|
1043
1146
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
1044
1147
|
if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
|
|
1045
1148
|
annotations.push(defaultAnnotation);
|
|
@@ -1055,22 +1158,36 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
1055
1158
|
provenance
|
|
1056
1159
|
};
|
|
1057
1160
|
}
|
|
1058
|
-
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting) {
|
|
1059
|
-
if (!
|
|
1161
|
+
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1162
|
+
if (!ts3.isIdentifier(prop.name)) {
|
|
1060
1163
|
return null;
|
|
1061
1164
|
}
|
|
1062
1165
|
const name = prop.name.text;
|
|
1063
1166
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1064
1167
|
const optional = prop.questionToken !== void 0;
|
|
1065
1168
|
const provenance = provenanceForNode(prop, file);
|
|
1066
|
-
let type = resolveTypeNode(
|
|
1169
|
+
let type = resolveTypeNode(
|
|
1170
|
+
tsType,
|
|
1171
|
+
checker,
|
|
1172
|
+
file,
|
|
1173
|
+
typeRegistry,
|
|
1174
|
+
visiting,
|
|
1175
|
+
prop,
|
|
1176
|
+
extensionRegistry
|
|
1177
|
+
);
|
|
1067
1178
|
const constraints = [];
|
|
1068
|
-
if (prop.type) {
|
|
1069
|
-
constraints.push(
|
|
1179
|
+
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
1180
|
+
constraints.push(
|
|
1181
|
+
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
1182
|
+
);
|
|
1070
1183
|
}
|
|
1071
|
-
constraints.push(
|
|
1184
|
+
constraints.push(
|
|
1185
|
+
...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
1186
|
+
);
|
|
1072
1187
|
let annotations = [];
|
|
1073
|
-
annotations.push(
|
|
1188
|
+
annotations.push(
|
|
1189
|
+
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
1190
|
+
);
|
|
1074
1191
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
1075
1192
|
return {
|
|
1076
1193
|
kind: "field",
|
|
@@ -1144,20 +1261,94 @@ function parseEnumMemberDisplayName(value) {
|
|
|
1144
1261
|
if (label === "") return null;
|
|
1145
1262
|
return { value: match[1], label };
|
|
1146
1263
|
}
|
|
1147
|
-
function
|
|
1148
|
-
if (
|
|
1264
|
+
function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
|
|
1265
|
+
if (sourceNode === void 0 || extensionRegistry === void 0) {
|
|
1266
|
+
return null;
|
|
1267
|
+
}
|
|
1268
|
+
const typeNode = extractTypeNodeFromSource(sourceNode);
|
|
1269
|
+
if (typeNode === void 0) {
|
|
1270
|
+
return null;
|
|
1271
|
+
}
|
|
1272
|
+
return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
|
|
1273
|
+
}
|
|
1274
|
+
function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
|
|
1275
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
1276
|
+
return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
|
|
1277
|
+
}
|
|
1278
|
+
const typeName = getTypeNodeRegistrationName(typeNode);
|
|
1279
|
+
if (typeName === null) {
|
|
1280
|
+
return null;
|
|
1281
|
+
}
|
|
1282
|
+
const registration = extensionRegistry.findTypeByName(typeName);
|
|
1283
|
+
if (registration !== void 0) {
|
|
1284
|
+
return {
|
|
1285
|
+
kind: "custom",
|
|
1286
|
+
typeId: `${registration.extensionId}/${registration.registration.typeName}`,
|
|
1287
|
+
payload: null
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
if (ts3.isTypeReferenceNode(typeNode) && ts3.isIdentifier(typeNode.typeName)) {
|
|
1291
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
1292
|
+
if (aliasDecl !== void 0) {
|
|
1293
|
+
return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
return null;
|
|
1297
|
+
}
|
|
1298
|
+
function extractTypeNodeFromSource(sourceNode) {
|
|
1299
|
+
if (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode) || ts3.isTypeAliasDeclaration(sourceNode)) {
|
|
1300
|
+
return sourceNode.type;
|
|
1301
|
+
}
|
|
1302
|
+
if (ts3.isTypeNode(sourceNode)) {
|
|
1303
|
+
return sourceNode;
|
|
1304
|
+
}
|
|
1305
|
+
return void 0;
|
|
1306
|
+
}
|
|
1307
|
+
function getTypeNodeRegistrationName(typeNode) {
|
|
1308
|
+
if (ts3.isTypeReferenceNode(typeNode)) {
|
|
1309
|
+
return ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
|
|
1310
|
+
}
|
|
1311
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
1312
|
+
return getTypeNodeRegistrationName(typeNode.type);
|
|
1313
|
+
}
|
|
1314
|
+
if (typeNode.kind === ts3.SyntaxKind.BigIntKeyword || typeNode.kind === ts3.SyntaxKind.StringKeyword || typeNode.kind === ts3.SyntaxKind.NumberKeyword || typeNode.kind === ts3.SyntaxKind.BooleanKeyword) {
|
|
1315
|
+
return typeNode.getText();
|
|
1316
|
+
}
|
|
1317
|
+
return null;
|
|
1318
|
+
}
|
|
1319
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1320
|
+
const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
|
|
1321
|
+
if (customType) {
|
|
1322
|
+
return customType;
|
|
1323
|
+
}
|
|
1324
|
+
const primitiveAlias = tryResolveNamedPrimitiveAlias(
|
|
1325
|
+
type,
|
|
1326
|
+
checker,
|
|
1327
|
+
file,
|
|
1328
|
+
typeRegistry,
|
|
1329
|
+
visiting,
|
|
1330
|
+
sourceNode,
|
|
1331
|
+
extensionRegistry
|
|
1332
|
+
);
|
|
1333
|
+
if (primitiveAlias) {
|
|
1334
|
+
return primitiveAlias;
|
|
1335
|
+
}
|
|
1336
|
+
if (type.flags & ts3.TypeFlags.String) {
|
|
1149
1337
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1150
1338
|
}
|
|
1151
|
-
if (type.flags &
|
|
1339
|
+
if (type.flags & ts3.TypeFlags.Number) {
|
|
1152
1340
|
return { kind: "primitive", primitiveKind: "number" };
|
|
1153
1341
|
}
|
|
1154
|
-
if (type.flags &
|
|
1342
|
+
if (type.flags & (ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral)) {
|
|
1343
|
+
return { kind: "primitive", primitiveKind: "bigint" };
|
|
1344
|
+
}
|
|
1345
|
+
if (type.flags & ts3.TypeFlags.Boolean) {
|
|
1155
1346
|
return { kind: "primitive", primitiveKind: "boolean" };
|
|
1156
1347
|
}
|
|
1157
|
-
if (type.flags &
|
|
1348
|
+
if (type.flags & ts3.TypeFlags.Null) {
|
|
1158
1349
|
return { kind: "primitive", primitiveKind: "null" };
|
|
1159
1350
|
}
|
|
1160
|
-
if (type.flags &
|
|
1351
|
+
if (type.flags & ts3.TypeFlags.Undefined) {
|
|
1161
1352
|
return { kind: "primitive", primitiveKind: "null" };
|
|
1162
1353
|
}
|
|
1163
1354
|
if (type.isStringLiteral()) {
|
|
@@ -1173,27 +1364,120 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
1173
1364
|
};
|
|
1174
1365
|
}
|
|
1175
1366
|
if (type.isUnion()) {
|
|
1176
|
-
return resolveUnionType(
|
|
1367
|
+
return resolveUnionType(
|
|
1368
|
+
type,
|
|
1369
|
+
checker,
|
|
1370
|
+
file,
|
|
1371
|
+
typeRegistry,
|
|
1372
|
+
visiting,
|
|
1373
|
+
sourceNode,
|
|
1374
|
+
extensionRegistry
|
|
1375
|
+
);
|
|
1177
1376
|
}
|
|
1178
1377
|
if (checker.isArrayType(type)) {
|
|
1179
|
-
return resolveArrayType(
|
|
1378
|
+
return resolveArrayType(
|
|
1379
|
+
type,
|
|
1380
|
+
checker,
|
|
1381
|
+
file,
|
|
1382
|
+
typeRegistry,
|
|
1383
|
+
visiting,
|
|
1384
|
+
sourceNode,
|
|
1385
|
+
extensionRegistry
|
|
1386
|
+
);
|
|
1180
1387
|
}
|
|
1181
1388
|
if (isObjectType(type)) {
|
|
1182
|
-
return resolveObjectType(type, checker, file, typeRegistry, visiting);
|
|
1389
|
+
return resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry);
|
|
1183
1390
|
}
|
|
1184
1391
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1185
1392
|
}
|
|
1186
|
-
function
|
|
1393
|
+
function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1394
|
+
if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
|
|
1395
|
+
return null;
|
|
1396
|
+
}
|
|
1397
|
+
const aliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration) ?? getReferencedTypeAliasDeclaration(sourceNode, checker);
|
|
1398
|
+
if (!aliasDecl) {
|
|
1399
|
+
return null;
|
|
1400
|
+
}
|
|
1401
|
+
const aliasName = aliasDecl.name.text;
|
|
1402
|
+
if (!typeRegistry[aliasName]) {
|
|
1403
|
+
const aliasType = checker.getTypeFromTypeNode(aliasDecl.type);
|
|
1404
|
+
const constraints = [
|
|
1405
|
+
...extractJSDocConstraintNodes(aliasDecl, file, makeParseOptions(extensionRegistry)),
|
|
1406
|
+
...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry)
|
|
1407
|
+
];
|
|
1408
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1409
|
+
aliasDecl,
|
|
1410
|
+
file,
|
|
1411
|
+
makeParseOptions(extensionRegistry)
|
|
1412
|
+
);
|
|
1413
|
+
typeRegistry[aliasName] = {
|
|
1414
|
+
name: aliasName,
|
|
1415
|
+
type: resolveAliasedPrimitiveTarget(
|
|
1416
|
+
aliasType,
|
|
1417
|
+
checker,
|
|
1418
|
+
file,
|
|
1419
|
+
typeRegistry,
|
|
1420
|
+
visiting,
|
|
1421
|
+
extensionRegistry
|
|
1422
|
+
),
|
|
1423
|
+
...constraints.length > 0 && { constraints },
|
|
1424
|
+
...annotations.length > 0 && { annotations },
|
|
1425
|
+
provenance: provenanceForDeclaration(aliasDecl, file)
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
return { kind: "reference", name: aliasName, typeArguments: [] };
|
|
1429
|
+
}
|
|
1430
|
+
function getReferencedTypeAliasDeclaration(sourceNode, checker) {
|
|
1431
|
+
const typeNode = sourceNode && (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode)) ? sourceNode.type : void 0;
|
|
1432
|
+
if (!typeNode || !ts3.isTypeReferenceNode(typeNode)) {
|
|
1433
|
+
return void 0;
|
|
1434
|
+
}
|
|
1435
|
+
return checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
1436
|
+
}
|
|
1437
|
+
function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
|
|
1438
|
+
if (!ts3.isTypeReferenceNode(typeNode)) {
|
|
1439
|
+
return false;
|
|
1440
|
+
}
|
|
1441
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
1442
|
+
if (!aliasDecl) {
|
|
1443
|
+
return false;
|
|
1444
|
+
}
|
|
1445
|
+
const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
|
|
1446
|
+
return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
|
|
1447
|
+
}
|
|
1448
|
+
function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1449
|
+
const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
1450
|
+
if (nestedAliasDecl !== void 0) {
|
|
1451
|
+
return resolveAliasedPrimitiveTarget(
|
|
1452
|
+
checker.getTypeFromTypeNode(nestedAliasDecl.type),
|
|
1453
|
+
checker,
|
|
1454
|
+
file,
|
|
1455
|
+
typeRegistry,
|
|
1456
|
+
visiting,
|
|
1457
|
+
extensionRegistry
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
return resolveTypeNode(type, checker, file, typeRegistry, visiting, void 0, extensionRegistry);
|
|
1461
|
+
}
|
|
1462
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1187
1463
|
const typeName = getNamedTypeName(type);
|
|
1188
1464
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
1189
1465
|
if (typeName && typeName in typeRegistry) {
|
|
1190
1466
|
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
1191
1467
|
}
|
|
1192
1468
|
const allTypes = type.types;
|
|
1469
|
+
const unionMemberTypeNodes = extractUnionMemberTypeNodes(sourceNode, checker);
|
|
1470
|
+
const nonNullSourceNodes = unionMemberTypeNodes.filter(
|
|
1471
|
+
(memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
|
|
1472
|
+
);
|
|
1193
1473
|
const nonNullTypes = allTypes.filter(
|
|
1194
|
-
(
|
|
1474
|
+
(memberType) => !(memberType.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
|
|
1195
1475
|
);
|
|
1196
|
-
const
|
|
1476
|
+
const nonNullMembers = nonNullTypes.map((memberType, index) => ({
|
|
1477
|
+
memberType,
|
|
1478
|
+
sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
|
|
1479
|
+
}));
|
|
1480
|
+
const hasNull = allTypes.some((t) => t.flags & ts3.TypeFlags.Null);
|
|
1197
1481
|
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1198
1482
|
if (namedDecl) {
|
|
1199
1483
|
for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
|
|
@@ -1209,7 +1493,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1209
1493
|
if (!typeName) {
|
|
1210
1494
|
return result;
|
|
1211
1495
|
}
|
|
1212
|
-
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
1496
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
1213
1497
|
typeRegistry[typeName] = {
|
|
1214
1498
|
name: typeName,
|
|
1215
1499
|
type: result,
|
|
@@ -1222,7 +1506,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1222
1506
|
const displayName = memberDisplayNames.get(String(value));
|
|
1223
1507
|
return displayName !== void 0 ? { value, displayName } : { value };
|
|
1224
1508
|
});
|
|
1225
|
-
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags &
|
|
1509
|
+
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts3.TypeFlags.BooleanLiteral);
|
|
1226
1510
|
if (isBooleanUnion2) {
|
|
1227
1511
|
const boolNode = { kind: "primitive", primitiveKind: "boolean" };
|
|
1228
1512
|
const result = hasNull ? {
|
|
@@ -1257,14 +1541,15 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1257
1541
|
} : enumNode;
|
|
1258
1542
|
return registerNamed(result);
|
|
1259
1543
|
}
|
|
1260
|
-
if (
|
|
1544
|
+
if (nonNullMembers.length === 1 && nonNullMembers[0]) {
|
|
1261
1545
|
const inner = resolveTypeNode(
|
|
1262
|
-
|
|
1546
|
+
nonNullMembers[0].memberType,
|
|
1263
1547
|
checker,
|
|
1264
1548
|
file,
|
|
1265
1549
|
typeRegistry,
|
|
1266
1550
|
visiting,
|
|
1267
|
-
sourceNode
|
|
1551
|
+
nonNullMembers[0].sourceNode ?? sourceNode,
|
|
1552
|
+
extensionRegistry
|
|
1268
1553
|
);
|
|
1269
1554
|
const result = hasNull ? {
|
|
1270
1555
|
kind: "union",
|
|
@@ -1272,29 +1557,54 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1272
1557
|
} : inner;
|
|
1273
1558
|
return registerNamed(result);
|
|
1274
1559
|
}
|
|
1275
|
-
const members =
|
|
1276
|
-
(
|
|
1560
|
+
const members = nonNullMembers.map(
|
|
1561
|
+
({ memberType, sourceNode: memberSourceNode }) => resolveTypeNode(
|
|
1562
|
+
memberType,
|
|
1563
|
+
checker,
|
|
1564
|
+
file,
|
|
1565
|
+
typeRegistry,
|
|
1566
|
+
visiting,
|
|
1567
|
+
memberSourceNode ?? sourceNode,
|
|
1568
|
+
extensionRegistry
|
|
1569
|
+
)
|
|
1277
1570
|
);
|
|
1278
1571
|
if (hasNull) {
|
|
1279
1572
|
members.push({ kind: "primitive", primitiveKind: "null" });
|
|
1280
1573
|
}
|
|
1281
1574
|
return registerNamed({ kind: "union", members });
|
|
1282
1575
|
}
|
|
1283
|
-
function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
1576
|
+
function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1284
1577
|
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
1285
1578
|
const elementType = typeArgs?.[0];
|
|
1286
|
-
const
|
|
1579
|
+
const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
|
|
1580
|
+
const items = elementType ? resolveTypeNode(
|
|
1581
|
+
elementType,
|
|
1582
|
+
checker,
|
|
1583
|
+
file,
|
|
1584
|
+
typeRegistry,
|
|
1585
|
+
visiting,
|
|
1586
|
+
elementSourceNode,
|
|
1587
|
+
extensionRegistry
|
|
1588
|
+
) : { kind: "primitive", primitiveKind: "string" };
|
|
1287
1589
|
return { kind: "array", items };
|
|
1288
1590
|
}
|
|
1289
|
-
function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
1591
|
+
function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1290
1592
|
if (type.getProperties().length > 0) {
|
|
1291
1593
|
return null;
|
|
1292
1594
|
}
|
|
1293
|
-
const indexInfo = checker.getIndexInfoOfType(type,
|
|
1595
|
+
const indexInfo = checker.getIndexInfoOfType(type, ts3.IndexKind.String);
|
|
1294
1596
|
if (!indexInfo) {
|
|
1295
1597
|
return null;
|
|
1296
1598
|
}
|
|
1297
|
-
const valueType = resolveTypeNode(
|
|
1599
|
+
const valueType = resolveTypeNode(
|
|
1600
|
+
indexInfo.type,
|
|
1601
|
+
checker,
|
|
1602
|
+
file,
|
|
1603
|
+
typeRegistry,
|
|
1604
|
+
visiting,
|
|
1605
|
+
void 0,
|
|
1606
|
+
extensionRegistry
|
|
1607
|
+
);
|
|
1298
1608
|
return { kind: "record", valueType };
|
|
1299
1609
|
}
|
|
1300
1610
|
function typeNodeContainsReference(type, targetName) {
|
|
@@ -1322,7 +1632,7 @@ function typeNodeContainsReference(type, targetName) {
|
|
|
1322
1632
|
}
|
|
1323
1633
|
}
|
|
1324
1634
|
}
|
|
1325
|
-
function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
1635
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1326
1636
|
const typeName = getNamedTypeName(type);
|
|
1327
1637
|
const namedTypeName = typeName ?? void 0;
|
|
1328
1638
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
@@ -1353,7 +1663,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1353
1663
|
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1354
1664
|
}
|
|
1355
1665
|
}
|
|
1356
|
-
const recordNode = tryResolveRecordType(
|
|
1666
|
+
const recordNode = tryResolveRecordType(
|
|
1667
|
+
type,
|
|
1668
|
+
checker,
|
|
1669
|
+
file,
|
|
1670
|
+
typeRegistry,
|
|
1671
|
+
visiting,
|
|
1672
|
+
extensionRegistry
|
|
1673
|
+
);
|
|
1357
1674
|
if (recordNode) {
|
|
1358
1675
|
visiting.delete(type);
|
|
1359
1676
|
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
@@ -1362,7 +1679,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1362
1679
|
clearNamedTypeRegistration();
|
|
1363
1680
|
return recordNode;
|
|
1364
1681
|
}
|
|
1365
|
-
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
1682
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
1366
1683
|
typeRegistry[namedTypeName] = {
|
|
1367
1684
|
name: namedTypeName,
|
|
1368
1685
|
type: recordNode,
|
|
@@ -1374,19 +1691,27 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1374
1691
|
return recordNode;
|
|
1375
1692
|
}
|
|
1376
1693
|
const properties = [];
|
|
1377
|
-
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
|
|
1694
|
+
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
|
|
1695
|
+
type,
|
|
1696
|
+
checker,
|
|
1697
|
+
file,
|
|
1698
|
+
typeRegistry,
|
|
1699
|
+
visiting,
|
|
1700
|
+
extensionRegistry
|
|
1701
|
+
);
|
|
1378
1702
|
for (const prop of type.getProperties()) {
|
|
1379
1703
|
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
1380
1704
|
if (!declaration) continue;
|
|
1381
1705
|
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
1382
|
-
const optional = !!(prop.flags &
|
|
1706
|
+
const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
|
|
1383
1707
|
const propTypeNode = resolveTypeNode(
|
|
1384
1708
|
propType,
|
|
1385
1709
|
checker,
|
|
1386
1710
|
file,
|
|
1387
1711
|
typeRegistry,
|
|
1388
1712
|
visiting,
|
|
1389
|
-
declaration
|
|
1713
|
+
declaration,
|
|
1714
|
+
extensionRegistry
|
|
1390
1715
|
);
|
|
1391
1716
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
1392
1717
|
properties.push({
|
|
@@ -1405,7 +1730,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1405
1730
|
additionalProperties: true
|
|
1406
1731
|
};
|
|
1407
1732
|
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
1408
|
-
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
1733
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
1409
1734
|
typeRegistry[namedTypeName] = {
|
|
1410
1735
|
name: namedTypeName,
|
|
1411
1736
|
type: objectNode,
|
|
@@ -1416,19 +1741,26 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1416
1741
|
}
|
|
1417
1742
|
return objectNode;
|
|
1418
1743
|
}
|
|
1419
|
-
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting) {
|
|
1744
|
+
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1420
1745
|
const symbols = [type.getSymbol(), type.aliasSymbol].filter(
|
|
1421
1746
|
(s) => s?.declarations != null && s.declarations.length > 0
|
|
1422
1747
|
);
|
|
1423
1748
|
for (const symbol of symbols) {
|
|
1424
1749
|
const declarations = symbol.declarations;
|
|
1425
1750
|
if (!declarations) continue;
|
|
1426
|
-
const classDecl = declarations.find(
|
|
1751
|
+
const classDecl = declarations.find(ts3.isClassDeclaration);
|
|
1427
1752
|
if (classDecl) {
|
|
1428
1753
|
const map = /* @__PURE__ */ new Map();
|
|
1429
1754
|
for (const member of classDecl.members) {
|
|
1430
|
-
if (
|
|
1431
|
-
const fieldNode = analyzeFieldToIR(
|
|
1755
|
+
if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name)) {
|
|
1756
|
+
const fieldNode = analyzeFieldToIR(
|
|
1757
|
+
member,
|
|
1758
|
+
checker,
|
|
1759
|
+
file,
|
|
1760
|
+
typeRegistry,
|
|
1761
|
+
visiting,
|
|
1762
|
+
extensionRegistry
|
|
1763
|
+
);
|
|
1432
1764
|
if (fieldNode) {
|
|
1433
1765
|
map.set(fieldNode.name, {
|
|
1434
1766
|
constraints: [...fieldNode.constraints],
|
|
@@ -1440,28 +1772,86 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1440
1772
|
}
|
|
1441
1773
|
return map;
|
|
1442
1774
|
}
|
|
1443
|
-
const interfaceDecl = declarations.find(
|
|
1775
|
+
const interfaceDecl = declarations.find(ts3.isInterfaceDeclaration);
|
|
1444
1776
|
if (interfaceDecl) {
|
|
1445
|
-
return buildFieldNodeInfoMap(
|
|
1777
|
+
return buildFieldNodeInfoMap(
|
|
1778
|
+
interfaceDecl.members,
|
|
1779
|
+
checker,
|
|
1780
|
+
file,
|
|
1781
|
+
typeRegistry,
|
|
1782
|
+
visiting,
|
|
1783
|
+
extensionRegistry
|
|
1784
|
+
);
|
|
1446
1785
|
}
|
|
1447
|
-
const typeAliasDecl = declarations.find(
|
|
1448
|
-
if (typeAliasDecl &&
|
|
1786
|
+
const typeAliasDecl = declarations.find(ts3.isTypeAliasDeclaration);
|
|
1787
|
+
if (typeAliasDecl && ts3.isTypeLiteralNode(typeAliasDecl.type)) {
|
|
1449
1788
|
return buildFieldNodeInfoMap(
|
|
1450
1789
|
typeAliasDecl.type.members,
|
|
1451
1790
|
checker,
|
|
1452
1791
|
file,
|
|
1453
1792
|
typeRegistry,
|
|
1454
|
-
visiting
|
|
1793
|
+
visiting,
|
|
1794
|
+
extensionRegistry
|
|
1455
1795
|
);
|
|
1456
1796
|
}
|
|
1457
1797
|
}
|
|
1458
1798
|
return null;
|
|
1459
1799
|
}
|
|
1460
|
-
function
|
|
1800
|
+
function extractArrayElementTypeNode(sourceNode, checker) {
|
|
1801
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
1802
|
+
if (typeNode === void 0) {
|
|
1803
|
+
return void 0;
|
|
1804
|
+
}
|
|
1805
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
1806
|
+
if (ts3.isArrayTypeNode(resolvedTypeNode)) {
|
|
1807
|
+
return resolvedTypeNode.elementType;
|
|
1808
|
+
}
|
|
1809
|
+
if (ts3.isTypeReferenceNode(resolvedTypeNode) && ts3.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
|
|
1810
|
+
return resolvedTypeNode.typeArguments[0];
|
|
1811
|
+
}
|
|
1812
|
+
return void 0;
|
|
1813
|
+
}
|
|
1814
|
+
function extractUnionMemberTypeNodes(sourceNode, checker) {
|
|
1815
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
1816
|
+
if (!typeNode) {
|
|
1817
|
+
return [];
|
|
1818
|
+
}
|
|
1819
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
1820
|
+
return ts3.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
|
|
1821
|
+
}
|
|
1822
|
+
function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
|
|
1823
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
1824
|
+
return resolveAliasedTypeNode(typeNode.type, checker, visited);
|
|
1825
|
+
}
|
|
1826
|
+
if (!ts3.isTypeReferenceNode(typeNode) || !ts3.isIdentifier(typeNode.typeName)) {
|
|
1827
|
+
return typeNode;
|
|
1828
|
+
}
|
|
1829
|
+
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
1830
|
+
const aliasDecl = symbol?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
1831
|
+
if (aliasDecl === void 0 || visited.has(aliasDecl)) {
|
|
1832
|
+
return typeNode;
|
|
1833
|
+
}
|
|
1834
|
+
visited.add(aliasDecl);
|
|
1835
|
+
return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
|
|
1836
|
+
}
|
|
1837
|
+
function isNullishTypeNode(typeNode) {
|
|
1838
|
+
if (typeNode.kind === ts3.SyntaxKind.NullKeyword || typeNode.kind === ts3.SyntaxKind.UndefinedKeyword) {
|
|
1839
|
+
return true;
|
|
1840
|
+
}
|
|
1841
|
+
return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
|
|
1842
|
+
}
|
|
1843
|
+
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1461
1844
|
const map = /* @__PURE__ */ new Map();
|
|
1462
1845
|
for (const member of members) {
|
|
1463
|
-
if (
|
|
1464
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1846
|
+
if (ts3.isPropertySignature(member)) {
|
|
1847
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1848
|
+
member,
|
|
1849
|
+
checker,
|
|
1850
|
+
file,
|
|
1851
|
+
typeRegistry,
|
|
1852
|
+
visiting,
|
|
1853
|
+
extensionRegistry
|
|
1854
|
+
);
|
|
1465
1855
|
if (fieldNode) {
|
|
1466
1856
|
map.set(fieldNode.name, {
|
|
1467
1857
|
constraints: [...fieldNode.constraints],
|
|
@@ -1474,8 +1864,8 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
|
1474
1864
|
return map;
|
|
1475
1865
|
}
|
|
1476
1866
|
var MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
1477
|
-
function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
1478
|
-
if (!
|
|
1867
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
|
|
1868
|
+
if (!ts3.isTypeReferenceNode(typeNode)) return [];
|
|
1479
1869
|
if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
|
|
1480
1870
|
const aliasName = typeNode.typeName.getText();
|
|
1481
1871
|
throw new Error(
|
|
@@ -1484,11 +1874,26 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
|
1484
1874
|
}
|
|
1485
1875
|
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
1486
1876
|
if (!symbol?.declarations) return [];
|
|
1487
|
-
const aliasDecl = symbol.declarations.find(
|
|
1877
|
+
const aliasDecl = symbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
1488
1878
|
if (!aliasDecl) return [];
|
|
1489
|
-
if (
|
|
1490
|
-
const
|
|
1491
|
-
|
|
1879
|
+
if (ts3.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
1880
|
+
const aliasFieldType = resolveTypeNode(
|
|
1881
|
+
checker.getTypeAtLocation(aliasDecl.type),
|
|
1882
|
+
checker,
|
|
1883
|
+
file,
|
|
1884
|
+
{},
|
|
1885
|
+
/* @__PURE__ */ new Set(),
|
|
1886
|
+
aliasDecl.type,
|
|
1887
|
+
extensionRegistry
|
|
1888
|
+
);
|
|
1889
|
+
const constraints = extractJSDocConstraintNodes(
|
|
1890
|
+
aliasDecl,
|
|
1891
|
+
file,
|
|
1892
|
+
makeParseOptions(extensionRegistry, aliasFieldType)
|
|
1893
|
+
);
|
|
1894
|
+
constraints.push(
|
|
1895
|
+
...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry, depth + 1)
|
|
1896
|
+
);
|
|
1492
1897
|
return constraints;
|
|
1493
1898
|
}
|
|
1494
1899
|
function provenanceForNode(node, file) {
|
|
@@ -1514,14 +1919,14 @@ function getNamedTypeName(type) {
|
|
|
1514
1919
|
const symbol = type.getSymbol();
|
|
1515
1920
|
if (symbol?.declarations) {
|
|
1516
1921
|
const decl = symbol.declarations[0];
|
|
1517
|
-
if (decl && (
|
|
1518
|
-
const name =
|
|
1922
|
+
if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
|
|
1923
|
+
const name = ts3.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
|
|
1519
1924
|
if (name) return name;
|
|
1520
1925
|
}
|
|
1521
1926
|
}
|
|
1522
1927
|
const aliasSymbol = type.aliasSymbol;
|
|
1523
1928
|
if (aliasSymbol?.declarations) {
|
|
1524
|
-
const aliasDecl = aliasSymbol.declarations.find(
|
|
1929
|
+
const aliasDecl = aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
1525
1930
|
if (aliasDecl) {
|
|
1526
1931
|
return aliasDecl.name.text;
|
|
1527
1932
|
}
|
|
@@ -1532,24 +1937,24 @@ function getNamedTypeDeclaration(type) {
|
|
|
1532
1937
|
const symbol = type.getSymbol();
|
|
1533
1938
|
if (symbol?.declarations) {
|
|
1534
1939
|
const decl = symbol.declarations[0];
|
|
1535
|
-
if (decl && (
|
|
1940
|
+
if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
|
|
1536
1941
|
return decl;
|
|
1537
1942
|
}
|
|
1538
1943
|
}
|
|
1539
1944
|
const aliasSymbol = type.aliasSymbol;
|
|
1540
1945
|
if (aliasSymbol?.declarations) {
|
|
1541
|
-
return aliasSymbol.declarations.find(
|
|
1946
|
+
return aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
1542
1947
|
}
|
|
1543
1948
|
return void 0;
|
|
1544
1949
|
}
|
|
1545
1950
|
function analyzeMethod(method, checker) {
|
|
1546
|
-
if (!
|
|
1951
|
+
if (!ts3.isIdentifier(method.name)) {
|
|
1547
1952
|
return null;
|
|
1548
1953
|
}
|
|
1549
1954
|
const name = method.name.text;
|
|
1550
1955
|
const parameters = [];
|
|
1551
1956
|
for (const param of method.parameters) {
|
|
1552
|
-
if (
|
|
1957
|
+
if (ts3.isIdentifier(param.name)) {
|
|
1553
1958
|
const paramInfo = analyzeParameter(param, checker);
|
|
1554
1959
|
parameters.push(paramInfo);
|
|
1555
1960
|
}
|
|
@@ -1560,7 +1965,7 @@ function analyzeMethod(method, checker) {
|
|
|
1560
1965
|
return { name, parameters, returnTypeNode, returnType };
|
|
1561
1966
|
}
|
|
1562
1967
|
function analyzeParameter(param, checker) {
|
|
1563
|
-
const name =
|
|
1968
|
+
const name = ts3.isIdentifier(param.name) ? param.name.text : "param";
|
|
1564
1969
|
const typeNode = param.type;
|
|
1565
1970
|
const type = checker.getTypeAtLocation(param);
|
|
1566
1971
|
const formSpecExportName = detectFormSpecReference(typeNode);
|
|
@@ -1569,20 +1974,90 @@ function analyzeParameter(param, checker) {
|
|
|
1569
1974
|
}
|
|
1570
1975
|
function detectFormSpecReference(typeNode) {
|
|
1571
1976
|
if (!typeNode) return null;
|
|
1572
|
-
if (!
|
|
1573
|
-
const typeName =
|
|
1977
|
+
if (!ts3.isTypeReferenceNode(typeNode)) return null;
|
|
1978
|
+
const typeName = ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts3.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
|
|
1574
1979
|
if (typeName !== "InferSchema" && typeName !== "InferFormSchema") return null;
|
|
1575
1980
|
const typeArg = typeNode.typeArguments?.[0];
|
|
1576
|
-
if (!typeArg || !
|
|
1577
|
-
if (
|
|
1981
|
+
if (!typeArg || !ts3.isTypeQueryNode(typeArg)) return null;
|
|
1982
|
+
if (ts3.isIdentifier(typeArg.exprName)) {
|
|
1578
1983
|
return typeArg.exprName.text;
|
|
1579
1984
|
}
|
|
1580
|
-
if (
|
|
1985
|
+
if (ts3.isQualifiedName(typeArg.exprName)) {
|
|
1581
1986
|
return typeArg.exprName.right.text;
|
|
1582
1987
|
}
|
|
1583
1988
|
return null;
|
|
1584
1989
|
}
|
|
1585
1990
|
|
|
1991
|
+
// src/analyzer/program.ts
|
|
1992
|
+
function createProgramContext(filePath) {
|
|
1993
|
+
const absolutePath = path.resolve(filePath);
|
|
1994
|
+
const fileDir = path.dirname(absolutePath);
|
|
1995
|
+
const configPath = ts4.findConfigFile(fileDir, ts4.sys.fileExists.bind(ts4.sys), "tsconfig.json");
|
|
1996
|
+
let compilerOptions;
|
|
1997
|
+
let fileNames;
|
|
1998
|
+
if (configPath) {
|
|
1999
|
+
const configFile = ts4.readConfigFile(configPath, ts4.sys.readFile.bind(ts4.sys));
|
|
2000
|
+
if (configFile.error) {
|
|
2001
|
+
throw new Error(
|
|
2002
|
+
`Error reading tsconfig.json: ${ts4.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
|
|
2003
|
+
);
|
|
2004
|
+
}
|
|
2005
|
+
const parsed = ts4.parseJsonConfigFileContent(
|
|
2006
|
+
configFile.config,
|
|
2007
|
+
ts4.sys,
|
|
2008
|
+
path.dirname(configPath)
|
|
2009
|
+
);
|
|
2010
|
+
if (parsed.errors.length > 0) {
|
|
2011
|
+
const errorMessages = parsed.errors.map((e) => ts4.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
|
|
2012
|
+
throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
|
|
2013
|
+
}
|
|
2014
|
+
compilerOptions = parsed.options;
|
|
2015
|
+
fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
|
|
2016
|
+
} else {
|
|
2017
|
+
compilerOptions = {
|
|
2018
|
+
target: ts4.ScriptTarget.ES2022,
|
|
2019
|
+
module: ts4.ModuleKind.NodeNext,
|
|
2020
|
+
moduleResolution: ts4.ModuleResolutionKind.NodeNext,
|
|
2021
|
+
strict: true,
|
|
2022
|
+
skipLibCheck: true,
|
|
2023
|
+
declaration: true
|
|
2024
|
+
};
|
|
2025
|
+
fileNames = [absolutePath];
|
|
2026
|
+
}
|
|
2027
|
+
const program = ts4.createProgram(fileNames, compilerOptions);
|
|
2028
|
+
const sourceFile = program.getSourceFile(absolutePath);
|
|
2029
|
+
if (!sourceFile) {
|
|
2030
|
+
throw new Error(`Could not find source file: ${absolutePath}`);
|
|
2031
|
+
}
|
|
2032
|
+
return {
|
|
2033
|
+
program,
|
|
2034
|
+
checker: program.getTypeChecker(),
|
|
2035
|
+
sourceFile
|
|
2036
|
+
};
|
|
2037
|
+
}
|
|
2038
|
+
function findNodeByName(sourceFile, name, predicate, getName) {
|
|
2039
|
+
let result = null;
|
|
2040
|
+
function visit(node) {
|
|
2041
|
+
if (result) return;
|
|
2042
|
+
if (predicate(node) && getName(node) === name) {
|
|
2043
|
+
result = node;
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
2046
|
+
ts4.forEachChild(node, visit);
|
|
2047
|
+
}
|
|
2048
|
+
visit(sourceFile);
|
|
2049
|
+
return result;
|
|
2050
|
+
}
|
|
2051
|
+
function findClassByName(sourceFile, className) {
|
|
2052
|
+
return findNodeByName(sourceFile, className, ts4.isClassDeclaration, (n) => n.name?.text);
|
|
2053
|
+
}
|
|
2054
|
+
function findInterfaceByName(sourceFile, interfaceName) {
|
|
2055
|
+
return findNodeByName(sourceFile, interfaceName, ts4.isInterfaceDeclaration, (n) => n.name.text);
|
|
2056
|
+
}
|
|
2057
|
+
function findTypeAliasByName(sourceFile, aliasName) {
|
|
2058
|
+
return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
|
|
2059
|
+
}
|
|
2060
|
+
|
|
1586
2061
|
// src/json-schema/ir-generator.ts
|
|
1587
2062
|
function makeContext(options) {
|
|
1588
2063
|
const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
|
|
@@ -1601,6 +2076,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
1601
2076
|
const ctx = makeContext(options);
|
|
1602
2077
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
1603
2078
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
2079
|
+
if (typeDef.constraints && typeDef.constraints.length > 0) {
|
|
2080
|
+
applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
|
|
2081
|
+
}
|
|
1604
2082
|
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
1605
2083
|
applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
|
|
1606
2084
|
}
|
|
@@ -1769,7 +2247,9 @@ function generateTypeNode(type, ctx) {
|
|
|
1769
2247
|
}
|
|
1770
2248
|
}
|
|
1771
2249
|
function generatePrimitiveType(type) {
|
|
1772
|
-
return {
|
|
2250
|
+
return {
|
|
2251
|
+
type: type.primitiveKind === "integer" || type.primitiveKind === "bigint" ? "integer" : type.primitiveKind
|
|
2252
|
+
};
|
|
1773
2253
|
}
|
|
1774
2254
|
function generateEnumType(type) {
|
|
1775
2255
|
const hasDisplayNames = type.members.some((m) => m.displayName !== void 0);
|
|
@@ -1942,7 +2422,7 @@ function applyAnnotations(schema, annotations, ctx) {
|
|
|
1942
2422
|
case "deprecated":
|
|
1943
2423
|
schema.deprecated = true;
|
|
1944
2424
|
if (annotation.message !== void 0 && annotation.message !== "") {
|
|
1945
|
-
schema[
|
|
2425
|
+
schema[`${ctx.vendorPrefix}-deprecation-description`] = annotation.message;
|
|
1946
2426
|
}
|
|
1947
2427
|
break;
|
|
1948
2428
|
case "placeholder":
|
|
@@ -1975,7 +2455,12 @@ function applyCustomConstraint(schema, constraint, ctx) {
|
|
|
1975
2455
|
`Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
|
|
1976
2456
|
);
|
|
1977
2457
|
}
|
|
1978
|
-
|
|
2458
|
+
assignVendorPrefixedExtensionKeywords(
|
|
2459
|
+
schema,
|
|
2460
|
+
registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
|
|
2461
|
+
ctx.vendorPrefix,
|
|
2462
|
+
`custom constraint "${constraint.constraintId}"`
|
|
2463
|
+
);
|
|
1979
2464
|
}
|
|
1980
2465
|
function applyCustomAnnotation(schema, annotation, ctx) {
|
|
1981
2466
|
const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
|
|
@@ -1987,7 +2472,22 @@ function applyCustomAnnotation(schema, annotation, ctx) {
|
|
|
1987
2472
|
if (registration.toJsonSchema === void 0) {
|
|
1988
2473
|
return;
|
|
1989
2474
|
}
|
|
1990
|
-
|
|
2475
|
+
assignVendorPrefixedExtensionKeywords(
|
|
2476
|
+
schema,
|
|
2477
|
+
registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
|
|
2478
|
+
ctx.vendorPrefix,
|
|
2479
|
+
`custom annotation "${annotation.annotationId}"`
|
|
2480
|
+
);
|
|
2481
|
+
}
|
|
2482
|
+
function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
|
|
2483
|
+
for (const [key, value] of Object.entries(extensionSchema)) {
|
|
2484
|
+
if (!key.startsWith(`${vendorPrefix}-`)) {
|
|
2485
|
+
throw new Error(
|
|
2486
|
+
`Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
|
|
2487
|
+
);
|
|
2488
|
+
}
|
|
2489
|
+
schema[key] = value;
|
|
2490
|
+
}
|
|
1991
2491
|
}
|
|
1992
2492
|
|
|
1993
2493
|
// src/ui-schema/schema.ts
|
|
@@ -2213,16 +2713,8 @@ function generateUiSchemaFromIR(ir) {
|
|
|
2213
2713
|
return parseOrThrow(uiSchema, result, "UI Schema");
|
|
2214
2714
|
}
|
|
2215
2715
|
|
|
2216
|
-
// src/generators/class-schema.ts
|
|
2217
|
-
function generateClassSchemas(analysis, source) {
|
|
2218
|
-
const ir = canonicalizeTSDoc(analysis, source);
|
|
2219
|
-
return {
|
|
2220
|
-
jsonSchema: generateJsonSchemaFromIR(ir),
|
|
2221
|
-
uiSchema: generateUiSchemaFromIR(ir)
|
|
2222
|
-
};
|
|
2223
|
-
}
|
|
2224
|
-
|
|
2225
2716
|
// src/validate/constraint-validator.ts
|
|
2717
|
+
import { normalizeConstraintTagName as normalizeConstraintTagName2 } from "@formspec/core";
|
|
2226
2718
|
function addContradiction(ctx, message, primary, related) {
|
|
2227
2719
|
ctx.diagnostics.push({
|
|
2228
2720
|
code: "CONTRADICTING_CONSTRAINTS",
|
|
@@ -2268,6 +2760,13 @@ function addConstraintBroadening(ctx, message, primary, related) {
|
|
|
2268
2760
|
relatedLocations: [related]
|
|
2269
2761
|
});
|
|
2270
2762
|
}
|
|
2763
|
+
function getExtensionIdFromConstraintId(constraintId) {
|
|
2764
|
+
const separator = constraintId.lastIndexOf("/");
|
|
2765
|
+
if (separator <= 0) {
|
|
2766
|
+
return null;
|
|
2767
|
+
}
|
|
2768
|
+
return constraintId.slice(0, separator);
|
|
2769
|
+
}
|
|
2271
2770
|
function findNumeric(constraints, constraintKind) {
|
|
2272
2771
|
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
2273
2772
|
}
|
|
@@ -2438,6 +2937,112 @@ function checkConstraintBroadening(ctx, fieldName, constraints) {
|
|
|
2438
2937
|
strongestByKey.set(key, constraint);
|
|
2439
2938
|
}
|
|
2440
2939
|
}
|
|
2940
|
+
function compareCustomConstraintStrength(current, previous) {
|
|
2941
|
+
const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
|
|
2942
|
+
const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
|
|
2943
|
+
switch (current.role.bound) {
|
|
2944
|
+
case "lower":
|
|
2945
|
+
return equalPayloadTiebreaker;
|
|
2946
|
+
case "upper":
|
|
2947
|
+
return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
|
|
2948
|
+
case "exact":
|
|
2949
|
+
return order === 0 ? 0 : Number.NaN;
|
|
2950
|
+
default: {
|
|
2951
|
+
const _exhaustive = current.role.bound;
|
|
2952
|
+
return _exhaustive;
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
function compareSemanticInclusivity(currentInclusive, previousInclusive) {
|
|
2957
|
+
if (currentInclusive === previousInclusive) {
|
|
2958
|
+
return 0;
|
|
2959
|
+
}
|
|
2960
|
+
return currentInclusive ? -1 : 1;
|
|
2961
|
+
}
|
|
2962
|
+
function customConstraintsContradict(lower, upper) {
|
|
2963
|
+
const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
|
|
2964
|
+
if (order > 0) {
|
|
2965
|
+
return true;
|
|
2966
|
+
}
|
|
2967
|
+
if (order < 0) {
|
|
2968
|
+
return false;
|
|
2969
|
+
}
|
|
2970
|
+
return !lower.role.inclusive || !upper.role.inclusive;
|
|
2971
|
+
}
|
|
2972
|
+
function describeCustomConstraintTag(constraint) {
|
|
2973
|
+
return constraint.provenance.tagName ?? constraint.constraintId;
|
|
2974
|
+
}
|
|
2975
|
+
function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
|
|
2976
|
+
if (ctx.extensionRegistry === void 0) {
|
|
2977
|
+
return;
|
|
2978
|
+
}
|
|
2979
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
2980
|
+
const lowerByFamily = /* @__PURE__ */ new Map();
|
|
2981
|
+
const upperByFamily = /* @__PURE__ */ new Map();
|
|
2982
|
+
for (const constraint of constraints) {
|
|
2983
|
+
if (constraint.constraintKind !== "custom") {
|
|
2984
|
+
continue;
|
|
2985
|
+
}
|
|
2986
|
+
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
2987
|
+
if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
|
|
2988
|
+
continue;
|
|
2989
|
+
}
|
|
2990
|
+
const entry = {
|
|
2991
|
+
constraint,
|
|
2992
|
+
comparePayloads: registration.comparePayloads,
|
|
2993
|
+
role: registration.semanticRole
|
|
2994
|
+
};
|
|
2995
|
+
const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
|
|
2996
|
+
const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
|
|
2997
|
+
const previous = strongestByKey.get(boundKey);
|
|
2998
|
+
if (previous !== void 0) {
|
|
2999
|
+
const strength = compareCustomConstraintStrength(entry, previous);
|
|
3000
|
+
if (Number.isNaN(strength)) {
|
|
3001
|
+
addContradiction(
|
|
3002
|
+
ctx,
|
|
3003
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
|
|
3004
|
+
constraint.provenance,
|
|
3005
|
+
previous.constraint.provenance
|
|
3006
|
+
);
|
|
3007
|
+
continue;
|
|
3008
|
+
}
|
|
3009
|
+
if (strength < 0) {
|
|
3010
|
+
addConstraintBroadening(
|
|
3011
|
+
ctx,
|
|
3012
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
|
|
3013
|
+
constraint.provenance,
|
|
3014
|
+
previous.constraint.provenance
|
|
3015
|
+
);
|
|
3016
|
+
continue;
|
|
3017
|
+
}
|
|
3018
|
+
if (strength > 0) {
|
|
3019
|
+
strongestByKey.set(boundKey, entry);
|
|
3020
|
+
}
|
|
3021
|
+
} else {
|
|
3022
|
+
strongestByKey.set(boundKey, entry);
|
|
3023
|
+
}
|
|
3024
|
+
if (registration.semanticRole.bound === "lower") {
|
|
3025
|
+
lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
3026
|
+
} else if (registration.semanticRole.bound === "upper") {
|
|
3027
|
+
upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
for (const [familyKey, lower] of lowerByFamily) {
|
|
3031
|
+
const upper = upperByFamily.get(familyKey);
|
|
3032
|
+
if (upper === void 0) {
|
|
3033
|
+
continue;
|
|
3034
|
+
}
|
|
3035
|
+
if (!customConstraintsContradict(lower, upper)) {
|
|
3036
|
+
continue;
|
|
3037
|
+
}
|
|
3038
|
+
addContradiction(
|
|
3039
|
+
ctx,
|
|
3040
|
+
`Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
|
|
3041
|
+
lower.constraint.provenance,
|
|
3042
|
+
upper.constraint.provenance
|
|
3043
|
+
);
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
2441
3046
|
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
2442
3047
|
const min = findNumeric(constraints, "minimum");
|
|
2443
3048
|
const max = findNumeric(constraints, "maximum");
|
|
@@ -2585,6 +3190,26 @@ function dereferenceType(ctx, type) {
|
|
|
2585
3190
|
}
|
|
2586
3191
|
return current;
|
|
2587
3192
|
}
|
|
3193
|
+
function collectReferencedTypeConstraints(ctx, type) {
|
|
3194
|
+
const collected = [];
|
|
3195
|
+
let current = type;
|
|
3196
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3197
|
+
while (current.kind === "reference") {
|
|
3198
|
+
if (seen.has(current.name)) {
|
|
3199
|
+
break;
|
|
3200
|
+
}
|
|
3201
|
+
seen.add(current.name);
|
|
3202
|
+
const definition = ctx.typeRegistry[current.name];
|
|
3203
|
+
if (definition === void 0) {
|
|
3204
|
+
break;
|
|
3205
|
+
}
|
|
3206
|
+
if (definition.constraints !== void 0) {
|
|
3207
|
+
collected.push(...definition.constraints);
|
|
3208
|
+
}
|
|
3209
|
+
current = definition.type;
|
|
3210
|
+
}
|
|
3211
|
+
return collected;
|
|
3212
|
+
}
|
|
2588
3213
|
function resolvePathTargetType(ctx, type, segments) {
|
|
2589
3214
|
const effectiveType = dereferenceType(ctx, type);
|
|
2590
3215
|
if (segments.length === 0) {
|
|
@@ -2606,12 +3231,33 @@ function resolvePathTargetType(ctx, type, segments) {
|
|
|
2606
3231
|
}
|
|
2607
3232
|
return { kind: "unresolvable", type: effectiveType };
|
|
2608
3233
|
}
|
|
3234
|
+
function isNullType(type) {
|
|
3235
|
+
return type.kind === "primitive" && type.primitiveKind === "null";
|
|
3236
|
+
}
|
|
3237
|
+
function collectCustomConstraintCandidateTypes(ctx, type) {
|
|
3238
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
3239
|
+
const candidates = [effectiveType];
|
|
3240
|
+
if (effectiveType.kind === "array") {
|
|
3241
|
+
candidates.push(...collectCustomConstraintCandidateTypes(ctx, effectiveType.items));
|
|
3242
|
+
}
|
|
3243
|
+
if (effectiveType.kind === "union") {
|
|
3244
|
+
const memberTypes = effectiveType.members.map((member) => dereferenceType(ctx, member));
|
|
3245
|
+
const nonNullMembers = memberTypes.filter((member) => !isNullType(member));
|
|
3246
|
+
if (nonNullMembers.length === 1 && nonNullMembers.length < memberTypes.length) {
|
|
3247
|
+
const [nullableMember] = nonNullMembers;
|
|
3248
|
+
if (nullableMember !== void 0) {
|
|
3249
|
+
candidates.push(...collectCustomConstraintCandidateTypes(ctx, nullableMember));
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
return candidates;
|
|
3254
|
+
}
|
|
2609
3255
|
function formatPathTargetFieldName(fieldName, path2) {
|
|
2610
3256
|
return path2.length === 0 ? fieldName : `${fieldName}.${path2.join(".")}`;
|
|
2611
3257
|
}
|
|
2612
3258
|
function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
2613
3259
|
const effectiveType = dereferenceType(ctx, type);
|
|
2614
|
-
const isNumber = effectiveType.kind === "primitive" &&
|
|
3260
|
+
const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
|
|
2615
3261
|
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
2616
3262
|
const isArray = effectiveType.kind === "array";
|
|
2617
3263
|
const isEnum = effectiveType.kind === "enum";
|
|
@@ -2669,7 +3315,9 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
|
2669
3315
|
break;
|
|
2670
3316
|
}
|
|
2671
3317
|
case "const": {
|
|
2672
|
-
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "boolean", "null"].includes(
|
|
3318
|
+
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
|
|
3319
|
+
effectiveType.primitiveKind
|
|
3320
|
+
) || effectiveType.kind === "enum";
|
|
2673
3321
|
if (!isPrimitiveConstType) {
|
|
2674
3322
|
addTypeMismatch(
|
|
2675
3323
|
ctx,
|
|
@@ -2680,7 +3328,8 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
|
2680
3328
|
}
|
|
2681
3329
|
if (effectiveType.kind === "primitive") {
|
|
2682
3330
|
const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
|
|
2683
|
-
|
|
3331
|
+
const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
|
|
3332
|
+
if (valueType !== expectedValueType) {
|
|
2684
3333
|
addTypeMismatch(
|
|
2685
3334
|
ctx,
|
|
2686
3335
|
`Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
|
|
@@ -2749,8 +3398,37 @@ function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
|
2749
3398
|
);
|
|
2750
3399
|
return;
|
|
2751
3400
|
}
|
|
2752
|
-
|
|
2753
|
-
|
|
3401
|
+
const candidateTypes = collectCustomConstraintCandidateTypes(ctx, type);
|
|
3402
|
+
const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : normalizeConstraintTagName2(constraint.provenance.tagName.replace(/^@/, ""));
|
|
3403
|
+
if (normalizedTagName !== void 0) {
|
|
3404
|
+
const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
|
|
3405
|
+
const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
|
|
3406
|
+
if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && !candidateTypes.some(
|
|
3407
|
+
(candidateType) => tagRegistration.registration.isApplicableToType?.(candidateType) !== false
|
|
3408
|
+
)) {
|
|
3409
|
+
addTypeMismatch(
|
|
3410
|
+
ctx,
|
|
3411
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3412
|
+
constraint.provenance
|
|
3413
|
+
);
|
|
3414
|
+
return;
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
if (registration.applicableTypes === null) {
|
|
3418
|
+
if (!candidateTypes.some((candidateType) => registration.isApplicableToType?.(candidateType) !== false)) {
|
|
3419
|
+
addTypeMismatch(
|
|
3420
|
+
ctx,
|
|
3421
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3422
|
+
constraint.provenance
|
|
3423
|
+
);
|
|
3424
|
+
}
|
|
3425
|
+
return;
|
|
3426
|
+
}
|
|
3427
|
+
const applicableTypes = registration.applicableTypes;
|
|
3428
|
+
const matchesApplicableType = candidateTypes.some(
|
|
3429
|
+
(candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
|
|
3430
|
+
);
|
|
3431
|
+
if (!matchesApplicableType) {
|
|
2754
3432
|
addTypeMismatch(
|
|
2755
3433
|
ctx,
|
|
2756
3434
|
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
@@ -2759,7 +3437,10 @@ function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
|
2759
3437
|
}
|
|
2760
3438
|
}
|
|
2761
3439
|
function validateFieldNode(ctx, field) {
|
|
2762
|
-
validateConstraints(ctx, field.name, field.type,
|
|
3440
|
+
validateConstraints(ctx, field.name, field.type, [
|
|
3441
|
+
...collectReferencedTypeConstraints(ctx, field.type),
|
|
3442
|
+
...field.constraints
|
|
3443
|
+
]);
|
|
2763
3444
|
if (field.type.kind === "object") {
|
|
2764
3445
|
for (const prop of field.type.properties) {
|
|
2765
3446
|
validateObjectProperty(ctx, field.name, prop);
|
|
@@ -2768,7 +3449,10 @@ function validateFieldNode(ctx, field) {
|
|
|
2768
3449
|
}
|
|
2769
3450
|
function validateObjectProperty(ctx, parentName, prop) {
|
|
2770
3451
|
const qualifiedName = `${parentName}.${prop.name}`;
|
|
2771
|
-
validateConstraints(ctx, qualifiedName, prop.type,
|
|
3452
|
+
validateConstraints(ctx, qualifiedName, prop.type, [
|
|
3453
|
+
...collectReferencedTypeConstraints(ctx, prop.type),
|
|
3454
|
+
...prop.constraints
|
|
3455
|
+
]);
|
|
2772
3456
|
if (prop.type.kind === "object") {
|
|
2773
3457
|
for (const nestedProp of prop.type.properties) {
|
|
2774
3458
|
validateObjectProperty(ctx, qualifiedName, nestedProp);
|
|
@@ -2781,6 +3465,7 @@ function validateConstraints(ctx, name, type, constraints) {
|
|
|
2781
3465
|
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
2782
3466
|
checkConstContradictions(ctx, name, constraints);
|
|
2783
3467
|
checkConstraintBroadening(ctx, name, constraints);
|
|
3468
|
+
checkCustomConstraintSemantics(ctx, name, constraints);
|
|
2784
3469
|
checkTypeApplicability(ctx, name, type, constraints);
|
|
2785
3470
|
}
|
|
2786
3471
|
function validateElement(ctx, element) {
|
|
@@ -2819,10 +3504,43 @@ function validateIR(ir, options) {
|
|
|
2819
3504
|
};
|
|
2820
3505
|
}
|
|
2821
3506
|
|
|
3507
|
+
// src/generators/class-schema.ts
|
|
3508
|
+
function generateClassSchemas(analysis, source, options) {
|
|
3509
|
+
const ir = canonicalizeTSDoc(analysis, source);
|
|
3510
|
+
const validationResult = validateIR(ir, {
|
|
3511
|
+
...options?.extensionRegistry !== void 0 && {
|
|
3512
|
+
extensionRegistry: options.extensionRegistry
|
|
3513
|
+
},
|
|
3514
|
+
...options?.vendorPrefix !== void 0 && { vendorPrefix: options.vendorPrefix }
|
|
3515
|
+
});
|
|
3516
|
+
if (!validationResult.valid) {
|
|
3517
|
+
throw new Error(formatValidationError(validationResult.diagnostics));
|
|
3518
|
+
}
|
|
3519
|
+
return {
|
|
3520
|
+
jsonSchema: generateJsonSchemaFromIR(ir, options),
|
|
3521
|
+
uiSchema: generateUiSchemaFromIR(ir)
|
|
3522
|
+
};
|
|
3523
|
+
}
|
|
3524
|
+
function formatValidationError(diagnostics) {
|
|
3525
|
+
const lines = diagnostics.map((diagnostic) => {
|
|
3526
|
+
const primary = formatLocation(diagnostic.primaryLocation);
|
|
3527
|
+
const related = diagnostic.relatedLocations.length > 0 ? ` [related: ${diagnostic.relatedLocations.map(formatLocation).join(", ")}]` : "";
|
|
3528
|
+
return `${diagnostic.code}: ${diagnostic.message} (${primary})${related}`;
|
|
3529
|
+
});
|
|
3530
|
+
return `FormSpec validation failed:
|
|
3531
|
+
${lines.map((line) => `- ${line}`).join("\n")}`;
|
|
3532
|
+
}
|
|
3533
|
+
function formatLocation(location) {
|
|
3534
|
+
return `${location.file}:${String(location.line)}:${String(location.column)}`;
|
|
3535
|
+
}
|
|
3536
|
+
|
|
2822
3537
|
// src/extensions/registry.ts
|
|
2823
3538
|
function createExtensionRegistry(extensions) {
|
|
2824
3539
|
const typeMap = /* @__PURE__ */ new Map();
|
|
3540
|
+
const typeNameMap = /* @__PURE__ */ new Map();
|
|
2825
3541
|
const constraintMap = /* @__PURE__ */ new Map();
|
|
3542
|
+
const constraintTagMap = /* @__PURE__ */ new Map();
|
|
3543
|
+
const builtinBroadeningMap = /* @__PURE__ */ new Map();
|
|
2826
3544
|
const annotationMap = /* @__PURE__ */ new Map();
|
|
2827
3545
|
for (const ext of extensions) {
|
|
2828
3546
|
if (ext.types !== void 0) {
|
|
@@ -2832,6 +3550,27 @@ function createExtensionRegistry(extensions) {
|
|
|
2832
3550
|
throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
|
|
2833
3551
|
}
|
|
2834
3552
|
typeMap.set(qualifiedId, type);
|
|
3553
|
+
for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
|
|
3554
|
+
if (typeNameMap.has(sourceTypeName)) {
|
|
3555
|
+
throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
|
|
3556
|
+
}
|
|
3557
|
+
typeNameMap.set(sourceTypeName, {
|
|
3558
|
+
extensionId: ext.extensionId,
|
|
3559
|
+
registration: type
|
|
3560
|
+
});
|
|
3561
|
+
}
|
|
3562
|
+
if (type.builtinConstraintBroadenings !== void 0) {
|
|
3563
|
+
for (const broadening of type.builtinConstraintBroadenings) {
|
|
3564
|
+
const key = `${qualifiedId}:${broadening.tagName}`;
|
|
3565
|
+
if (builtinBroadeningMap.has(key)) {
|
|
3566
|
+
throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
|
|
3567
|
+
}
|
|
3568
|
+
builtinBroadeningMap.set(key, {
|
|
3569
|
+
extensionId: ext.extensionId,
|
|
3570
|
+
registration: broadening
|
|
3571
|
+
});
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
2835
3574
|
}
|
|
2836
3575
|
}
|
|
2837
3576
|
if (ext.constraints !== void 0) {
|
|
@@ -2843,6 +3582,17 @@ function createExtensionRegistry(extensions) {
|
|
|
2843
3582
|
constraintMap.set(qualifiedId, constraint);
|
|
2844
3583
|
}
|
|
2845
3584
|
}
|
|
3585
|
+
if (ext.constraintTags !== void 0) {
|
|
3586
|
+
for (const tag of ext.constraintTags) {
|
|
3587
|
+
if (constraintTagMap.has(tag.tagName)) {
|
|
3588
|
+
throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
|
|
3589
|
+
}
|
|
3590
|
+
constraintTagMap.set(tag.tagName, {
|
|
3591
|
+
extensionId: ext.extensionId,
|
|
3592
|
+
registration: tag
|
|
3593
|
+
});
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
2846
3596
|
if (ext.annotations !== void 0) {
|
|
2847
3597
|
for (const annotation of ext.annotations) {
|
|
2848
3598
|
const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
|
|
@@ -2856,7 +3606,10 @@ function createExtensionRegistry(extensions) {
|
|
|
2856
3606
|
return {
|
|
2857
3607
|
extensions,
|
|
2858
3608
|
findType: (typeId) => typeMap.get(typeId),
|
|
3609
|
+
findTypeByName: (typeName) => typeNameMap.get(typeName),
|
|
2859
3610
|
findConstraint: (constraintId) => constraintMap.get(constraintId),
|
|
3611
|
+
findConstraintTag: (tagName) => constraintTagMap.get(tagName),
|
|
3612
|
+
findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
|
|
2860
3613
|
findAnnotation: (annotationId) => annotationMap.get(annotationId)
|
|
2861
3614
|
};
|
|
2862
3615
|
}
|
|
@@ -2882,6 +3635,7 @@ function typeToJsonSchema(type, checker) {
|
|
|
2882
3635
|
provenance: fieldProvenance
|
|
2883
3636
|
}
|
|
2884
3637
|
],
|
|
3638
|
+
rootAnnotations: [],
|
|
2885
3639
|
typeRegistry,
|
|
2886
3640
|
provenance: fieldProvenance
|
|
2887
3641
|
};
|