@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.cjs
CHANGED
|
@@ -71,6 +71,7 @@ function canonicalizeChainDSL(form) {
|
|
|
71
71
|
kind: "form-ir",
|
|
72
72
|
irVersion: import_core.IR_VERSION,
|
|
73
73
|
elements: canonicalizeElements(form.elements),
|
|
74
|
+
rootAnnotations: [],
|
|
74
75
|
typeRegistry: {},
|
|
75
76
|
provenance: CHAIN_DSL_PROVENANCE
|
|
76
77
|
};
|
|
@@ -376,6 +377,7 @@ function canonicalizeTSDoc(analysis, source) {
|
|
|
376
377
|
irVersion: import_core2.IR_VERSION,
|
|
377
378
|
elements,
|
|
378
379
|
typeRegistry: analysis.typeRegistry,
|
|
380
|
+
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { rootAnnotations: analysis.annotations },
|
|
379
381
|
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
|
|
380
382
|
provenance
|
|
381
383
|
};
|
|
@@ -436,85 +438,17 @@ function wrapInConditional(field, layout, provenance) {
|
|
|
436
438
|
}
|
|
437
439
|
|
|
438
440
|
// src/analyzer/program.ts
|
|
439
|
-
var
|
|
441
|
+
var ts4 = __toESM(require("typescript"), 1);
|
|
440
442
|
var path = __toESM(require("path"), 1);
|
|
441
|
-
function createProgramContext(filePath) {
|
|
442
|
-
const absolutePath = path.resolve(filePath);
|
|
443
|
-
const fileDir = path.dirname(absolutePath);
|
|
444
|
-
const configPath = ts.findConfigFile(fileDir, ts.sys.fileExists.bind(ts.sys), "tsconfig.json");
|
|
445
|
-
let compilerOptions;
|
|
446
|
-
let fileNames;
|
|
447
|
-
if (configPath) {
|
|
448
|
-
const configFile = ts.readConfigFile(configPath, ts.sys.readFile.bind(ts.sys));
|
|
449
|
-
if (configFile.error) {
|
|
450
|
-
throw new Error(
|
|
451
|
-
`Error reading tsconfig.json: ${ts.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
|
|
452
|
-
);
|
|
453
|
-
}
|
|
454
|
-
const parsed = ts.parseJsonConfigFileContent(
|
|
455
|
-
configFile.config,
|
|
456
|
-
ts.sys,
|
|
457
|
-
path.dirname(configPath)
|
|
458
|
-
);
|
|
459
|
-
if (parsed.errors.length > 0) {
|
|
460
|
-
const errorMessages = parsed.errors.map((e) => ts.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
|
|
461
|
-
throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
|
|
462
|
-
}
|
|
463
|
-
compilerOptions = parsed.options;
|
|
464
|
-
fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
|
|
465
|
-
} else {
|
|
466
|
-
compilerOptions = {
|
|
467
|
-
target: ts.ScriptTarget.ES2022,
|
|
468
|
-
module: ts.ModuleKind.NodeNext,
|
|
469
|
-
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
470
|
-
strict: true,
|
|
471
|
-
skipLibCheck: true,
|
|
472
|
-
declaration: true
|
|
473
|
-
};
|
|
474
|
-
fileNames = [absolutePath];
|
|
475
|
-
}
|
|
476
|
-
const program = ts.createProgram(fileNames, compilerOptions);
|
|
477
|
-
const sourceFile = program.getSourceFile(absolutePath);
|
|
478
|
-
if (!sourceFile) {
|
|
479
|
-
throw new Error(`Could not find source file: ${absolutePath}`);
|
|
480
|
-
}
|
|
481
|
-
return {
|
|
482
|
-
program,
|
|
483
|
-
checker: program.getTypeChecker(),
|
|
484
|
-
sourceFile
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
function findNodeByName(sourceFile, name, predicate, getName) {
|
|
488
|
-
let result = null;
|
|
489
|
-
function visit(node) {
|
|
490
|
-
if (result) return;
|
|
491
|
-
if (predicate(node) && getName(node) === name) {
|
|
492
|
-
result = node;
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
ts.forEachChild(node, visit);
|
|
496
|
-
}
|
|
497
|
-
visit(sourceFile);
|
|
498
|
-
return result;
|
|
499
|
-
}
|
|
500
|
-
function findClassByName(sourceFile, className) {
|
|
501
|
-
return findNodeByName(sourceFile, className, ts.isClassDeclaration, (n) => n.name?.text);
|
|
502
|
-
}
|
|
503
|
-
function findInterfaceByName(sourceFile, interfaceName) {
|
|
504
|
-
return findNodeByName(sourceFile, interfaceName, ts.isInterfaceDeclaration, (n) => n.name.text);
|
|
505
|
-
}
|
|
506
|
-
function findTypeAliasByName(sourceFile, aliasName) {
|
|
507
|
-
return findNodeByName(sourceFile, aliasName, ts.isTypeAliasDeclaration, (n) => n.name.text);
|
|
508
|
-
}
|
|
509
443
|
|
|
510
444
|
// src/analyzer/class-analyzer.ts
|
|
511
|
-
var
|
|
445
|
+
var ts3 = __toESM(require("typescript"), 1);
|
|
512
446
|
|
|
513
447
|
// src/analyzer/jsdoc-constraints.ts
|
|
514
|
-
var
|
|
448
|
+
var ts2 = __toESM(require("typescript"), 1);
|
|
515
449
|
|
|
516
450
|
// src/analyzer/tsdoc-parser.ts
|
|
517
|
-
var
|
|
451
|
+
var ts = __toESM(require("typescript"), 1);
|
|
518
452
|
var import_tsdoc = require("@microsoft/tsdoc");
|
|
519
453
|
var import_core3 = require("@formspec/core");
|
|
520
454
|
|
|
@@ -542,7 +476,7 @@ var LENGTH_CONSTRAINT_MAP = {
|
|
|
542
476
|
maxItems: "maxItems"
|
|
543
477
|
};
|
|
544
478
|
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
|
|
545
|
-
function createFormSpecTSDocConfig() {
|
|
479
|
+
function createFormSpecTSDocConfig(extensionTagNames = []) {
|
|
546
480
|
const config = new import_tsdoc.TSDocConfiguration();
|
|
547
481
|
for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
548
482
|
config.addTagDefinition(
|
|
@@ -562,14 +496,34 @@ function createFormSpecTSDocConfig() {
|
|
|
562
496
|
})
|
|
563
497
|
);
|
|
564
498
|
}
|
|
499
|
+
for (const tagName of extensionTagNames) {
|
|
500
|
+
config.addTagDefinition(
|
|
501
|
+
new import_tsdoc.TSDocTagDefinition({
|
|
502
|
+
tagName: "@" + tagName,
|
|
503
|
+
syntaxKind: import_tsdoc.TSDocTagSyntaxKind.BlockTag,
|
|
504
|
+
allowMultiple: true
|
|
505
|
+
})
|
|
506
|
+
);
|
|
507
|
+
}
|
|
565
508
|
return config;
|
|
566
509
|
}
|
|
567
|
-
var
|
|
568
|
-
function getParser() {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
510
|
+
var parserCache = /* @__PURE__ */ new Map();
|
|
511
|
+
function getParser(options) {
|
|
512
|
+
const extensionTagNames = [
|
|
513
|
+
...options?.extensionRegistry?.extensions.flatMap(
|
|
514
|
+
(extension) => (extension.constraintTags ?? []).map((tag) => tag.tagName)
|
|
515
|
+
) ?? []
|
|
516
|
+
].sort();
|
|
517
|
+
const cacheKey = extensionTagNames.join("|");
|
|
518
|
+
const existing = parserCache.get(cacheKey);
|
|
519
|
+
if (existing) {
|
|
520
|
+
return existing;
|
|
521
|
+
}
|
|
522
|
+
const parser = new import_tsdoc.TSDocParser(createFormSpecTSDocConfig(extensionTagNames));
|
|
523
|
+
parserCache.set(cacheKey, parser);
|
|
524
|
+
return parser;
|
|
525
|
+
}
|
|
526
|
+
function parseTSDocTags(node, file = "", options) {
|
|
573
527
|
const constraints = [];
|
|
574
528
|
const annotations = [];
|
|
575
529
|
let displayName;
|
|
@@ -580,17 +534,17 @@ function parseTSDocTags(node, file = "") {
|
|
|
580
534
|
let placeholderProvenance;
|
|
581
535
|
const sourceFile = node.getSourceFile();
|
|
582
536
|
const sourceText = sourceFile.getFullText();
|
|
583
|
-
const commentRanges =
|
|
537
|
+
const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
584
538
|
if (commentRanges) {
|
|
585
539
|
for (const range of commentRanges) {
|
|
586
|
-
if (range.kind !==
|
|
540
|
+
if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
|
|
587
541
|
continue;
|
|
588
542
|
}
|
|
589
543
|
const commentText = sourceText.substring(range.pos, range.end);
|
|
590
544
|
if (!commentText.startsWith("/**")) {
|
|
591
545
|
continue;
|
|
592
546
|
}
|
|
593
|
-
const parser = getParser();
|
|
547
|
+
const parser = getParser(options);
|
|
594
548
|
const parserContext = parser.parseRange(
|
|
595
549
|
import_tsdoc.TextRange.fromStringRange(sourceText, range.pos, range.end)
|
|
596
550
|
);
|
|
@@ -601,26 +555,31 @@ function parseTSDocTags(node, file = "") {
|
|
|
601
555
|
const text2 = extractBlockText(block).trim();
|
|
602
556
|
if (text2 === "") continue;
|
|
603
557
|
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
displayName
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
558
|
+
switch (tagName) {
|
|
559
|
+
case "displayName":
|
|
560
|
+
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
561
|
+
displayName = text2;
|
|
562
|
+
displayNameProvenance = provenance2;
|
|
563
|
+
}
|
|
564
|
+
break;
|
|
565
|
+
case "format":
|
|
566
|
+
annotations.push({
|
|
567
|
+
kind: "annotation",
|
|
568
|
+
annotationKind: "format",
|
|
569
|
+
value: text2,
|
|
570
|
+
provenance: provenance2
|
|
571
|
+
});
|
|
572
|
+
break;
|
|
573
|
+
case "description":
|
|
618
574
|
description = text2;
|
|
619
575
|
descriptionProvenance = provenance2;
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
576
|
+
break;
|
|
577
|
+
case "placeholder":
|
|
578
|
+
if (placeholder === void 0) {
|
|
579
|
+
placeholder = text2;
|
|
580
|
+
placeholderProvenance = provenance2;
|
|
581
|
+
}
|
|
582
|
+
break;
|
|
624
583
|
}
|
|
625
584
|
continue;
|
|
626
585
|
}
|
|
@@ -629,7 +588,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
629
588
|
const expectedType = (0, import_core3.isBuiltinConstraintName)(tagName) ? import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
|
|
630
589
|
if (text === "" && expectedType !== "boolean") continue;
|
|
631
590
|
const provenance = provenanceForComment(range, sourceFile, file, tagName);
|
|
632
|
-
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
591
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance, options);
|
|
633
592
|
if (constraintNode) {
|
|
634
593
|
constraints.push(constraintNode);
|
|
635
594
|
}
|
|
@@ -650,6 +609,13 @@ function parseTSDocTags(node, file = "") {
|
|
|
650
609
|
descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
|
|
651
610
|
}
|
|
652
611
|
}
|
|
612
|
+
if (description === void 0) {
|
|
613
|
+
const summary = extractPlainText(docComment.summarySection).trim();
|
|
614
|
+
if (summary !== "") {
|
|
615
|
+
description = summary;
|
|
616
|
+
descriptionProvenance = provenanceForComment(range, sourceFile, file, "summary");
|
|
617
|
+
}
|
|
618
|
+
}
|
|
653
619
|
}
|
|
654
620
|
}
|
|
655
621
|
if (displayName !== void 0 && displayNameProvenance !== void 0) {
|
|
@@ -676,7 +642,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
676
642
|
provenance: placeholderProvenance
|
|
677
643
|
});
|
|
678
644
|
}
|
|
679
|
-
const jsDocTagsAll =
|
|
645
|
+
const jsDocTagsAll = ts.getJSDocTags(node);
|
|
680
646
|
for (const tag of jsDocTagsAll) {
|
|
681
647
|
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
682
648
|
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
@@ -689,7 +655,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
689
655
|
annotations.push(defaultValueNode);
|
|
690
656
|
continue;
|
|
691
657
|
}
|
|
692
|
-
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
658
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance, options);
|
|
693
659
|
if (constraintNode) {
|
|
694
660
|
constraints.push(constraintNode);
|
|
695
661
|
}
|
|
@@ -699,7 +665,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
699
665
|
function extractDisplayNameMetadata(node) {
|
|
700
666
|
let displayName;
|
|
701
667
|
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
702
|
-
for (const tag of
|
|
668
|
+
for (const tag of ts.getJSDocTags(node)) {
|
|
703
669
|
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
704
670
|
if (tagName !== "displayName") continue;
|
|
705
671
|
const commentText = getTagCommentText(tag);
|
|
@@ -720,11 +686,11 @@ function extractDisplayNameMetadata(node) {
|
|
|
720
686
|
}
|
|
721
687
|
function extractPathTarget(text) {
|
|
722
688
|
const trimmed = text.trimStart();
|
|
723
|
-
const match = /^:([a-zA-Z_]\w*)
|
|
724
|
-
if (!match?.[1]
|
|
689
|
+
const match = /^:([a-zA-Z_]\w*)(?:\s+([\s\S]*))?$/.exec(trimmed);
|
|
690
|
+
if (!match?.[1]) return null;
|
|
725
691
|
return {
|
|
726
692
|
path: { segments: [match[1]] },
|
|
727
|
-
remainingText: match[2]
|
|
693
|
+
remainingText: match[2] ?? ""
|
|
728
694
|
};
|
|
729
695
|
}
|
|
730
696
|
function extractBlockText(block) {
|
|
@@ -745,7 +711,11 @@ function extractPlainText(node) {
|
|
|
745
711
|
}
|
|
746
712
|
return result;
|
|
747
713
|
}
|
|
748
|
-
function parseConstraintValue(tagName, text, provenance) {
|
|
714
|
+
function parseConstraintValue(tagName, text, provenance, options) {
|
|
715
|
+
const customConstraint = parseExtensionConstraintValue(tagName, text, provenance, options);
|
|
716
|
+
if (customConstraint) {
|
|
717
|
+
return customConstraint;
|
|
718
|
+
}
|
|
749
719
|
if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
|
|
750
720
|
return null;
|
|
751
721
|
}
|
|
@@ -850,6 +820,83 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
850
820
|
provenance
|
|
851
821
|
};
|
|
852
822
|
}
|
|
823
|
+
function parseExtensionConstraintValue(tagName, text, provenance, options) {
|
|
824
|
+
const pathResult = extractPathTarget(text);
|
|
825
|
+
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
826
|
+
const path2 = pathResult?.path;
|
|
827
|
+
const registry = options?.extensionRegistry;
|
|
828
|
+
if (registry === void 0) {
|
|
829
|
+
return null;
|
|
830
|
+
}
|
|
831
|
+
const directTag = registry.findConstraintTag(tagName);
|
|
832
|
+
if (directTag !== void 0) {
|
|
833
|
+
return makeCustomConstraintNode(
|
|
834
|
+
directTag.extensionId,
|
|
835
|
+
directTag.registration.constraintName,
|
|
836
|
+
directTag.registration.parseValue(effectiveText),
|
|
837
|
+
provenance,
|
|
838
|
+
path2,
|
|
839
|
+
registry
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
|
|
843
|
+
return null;
|
|
844
|
+
}
|
|
845
|
+
const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
|
|
846
|
+
if (broadenedTypeId === void 0) {
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
|
|
850
|
+
if (broadened === void 0) {
|
|
851
|
+
return null;
|
|
852
|
+
}
|
|
853
|
+
return makeCustomConstraintNode(
|
|
854
|
+
broadened.extensionId,
|
|
855
|
+
broadened.registration.constraintName,
|
|
856
|
+
broadened.registration.parseValue(effectiveText),
|
|
857
|
+
provenance,
|
|
858
|
+
path2,
|
|
859
|
+
registry
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
function getBroadenedCustomTypeId(fieldType) {
|
|
863
|
+
if (fieldType?.kind === "custom") {
|
|
864
|
+
return fieldType.typeId;
|
|
865
|
+
}
|
|
866
|
+
if (fieldType?.kind !== "union") {
|
|
867
|
+
return void 0;
|
|
868
|
+
}
|
|
869
|
+
const customMembers = fieldType.members.filter(
|
|
870
|
+
(member) => member.kind === "custom"
|
|
871
|
+
);
|
|
872
|
+
if (customMembers.length !== 1) {
|
|
873
|
+
return void 0;
|
|
874
|
+
}
|
|
875
|
+
const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
|
|
876
|
+
const allOtherMembersAreNull = nonCustomMembers.every(
|
|
877
|
+
(member) => member.kind === "primitive" && member.primitiveKind === "null"
|
|
878
|
+
);
|
|
879
|
+
const customMember = customMembers[0];
|
|
880
|
+
return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
|
|
881
|
+
}
|
|
882
|
+
function makeCustomConstraintNode(extensionId, constraintName, payload, provenance, path2, registry) {
|
|
883
|
+
const constraintId = `${extensionId}/${constraintName}`;
|
|
884
|
+
const registration = registry.findConstraint(constraintId);
|
|
885
|
+
if (registration === void 0) {
|
|
886
|
+
throw new Error(
|
|
887
|
+
`Custom TSDoc tag resolved to unregistered constraint "${constraintId}". Register the constraint before using its tag.`
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
return {
|
|
891
|
+
kind: "constraint",
|
|
892
|
+
constraintKind: "custom",
|
|
893
|
+
constraintId,
|
|
894
|
+
payload,
|
|
895
|
+
compositionRule: registration.compositionRule,
|
|
896
|
+
...path2 && { path: path2 },
|
|
897
|
+
provenance
|
|
898
|
+
};
|
|
899
|
+
}
|
|
853
900
|
function parseDefaultValueValue(text, provenance) {
|
|
854
901
|
const trimmed = text.trim();
|
|
855
902
|
let value;
|
|
@@ -906,33 +953,33 @@ function getTagCommentText(tag) {
|
|
|
906
953
|
if (typeof tag.comment === "string") {
|
|
907
954
|
return tag.comment;
|
|
908
955
|
}
|
|
909
|
-
return
|
|
956
|
+
return ts.getTextOfJSDocComment(tag.comment);
|
|
910
957
|
}
|
|
911
958
|
|
|
912
959
|
// src/analyzer/jsdoc-constraints.ts
|
|
913
|
-
function extractJSDocConstraintNodes(node, file = "") {
|
|
914
|
-
const result = parseTSDocTags(node, file);
|
|
960
|
+
function extractJSDocConstraintNodes(node, file = "", options) {
|
|
961
|
+
const result = parseTSDocTags(node, file, options);
|
|
915
962
|
return [...result.constraints];
|
|
916
963
|
}
|
|
917
|
-
function extractJSDocAnnotationNodes(node, file = "") {
|
|
918
|
-
const result = parseTSDocTags(node, file);
|
|
964
|
+
function extractJSDocAnnotationNodes(node, file = "", options) {
|
|
965
|
+
const result = parseTSDocTags(node, file, options);
|
|
919
966
|
return [...result.annotations];
|
|
920
967
|
}
|
|
921
968
|
function extractDefaultValueAnnotation(initializer, file = "") {
|
|
922
969
|
if (!initializer) return null;
|
|
923
970
|
let value;
|
|
924
|
-
if (
|
|
971
|
+
if (ts2.isStringLiteral(initializer)) {
|
|
925
972
|
value = initializer.text;
|
|
926
|
-
} else if (
|
|
973
|
+
} else if (ts2.isNumericLiteral(initializer)) {
|
|
927
974
|
value = Number(initializer.text);
|
|
928
|
-
} else if (initializer.kind ===
|
|
975
|
+
} else if (initializer.kind === ts2.SyntaxKind.TrueKeyword) {
|
|
929
976
|
value = true;
|
|
930
|
-
} else if (initializer.kind ===
|
|
977
|
+
} else if (initializer.kind === ts2.SyntaxKind.FalseKeyword) {
|
|
931
978
|
value = false;
|
|
932
|
-
} else if (initializer.kind ===
|
|
979
|
+
} else if (initializer.kind === ts2.SyntaxKind.NullKeyword) {
|
|
933
980
|
value = null;
|
|
934
|
-
} else if (
|
|
935
|
-
if (initializer.operator ===
|
|
981
|
+
} else if (ts2.isPrefixUnaryExpression(initializer)) {
|
|
982
|
+
if (initializer.operator === ts2.SyntaxKind.MinusToken && ts2.isNumericLiteral(initializer.operand)) {
|
|
936
983
|
value = -Number(initializer.operand.text);
|
|
937
984
|
}
|
|
938
985
|
}
|
|
@@ -954,36 +1001,56 @@ function extractDefaultValueAnnotation(initializer, file = "") {
|
|
|
954
1001
|
|
|
955
1002
|
// src/analyzer/class-analyzer.ts
|
|
956
1003
|
function isObjectType(type) {
|
|
957
|
-
return !!(type.flags &
|
|
1004
|
+
return !!(type.flags & ts3.TypeFlags.Object);
|
|
958
1005
|
}
|
|
959
1006
|
function isTypeReference(type) {
|
|
960
|
-
return !!(type.flags &
|
|
1007
|
+
return !!(type.flags & ts3.TypeFlags.Object) && !!(type.objectFlags & ts3.ObjectFlags.Reference);
|
|
961
1008
|
}
|
|
962
1009
|
var RESOLVING_TYPE_PLACEHOLDER = {
|
|
963
1010
|
kind: "object",
|
|
964
1011
|
properties: [],
|
|
965
1012
|
additionalProperties: true
|
|
966
1013
|
};
|
|
967
|
-
function
|
|
1014
|
+
function makeParseOptions(extensionRegistry, fieldType) {
|
|
1015
|
+
if (extensionRegistry === void 0 && fieldType === void 0) {
|
|
1016
|
+
return void 0;
|
|
1017
|
+
}
|
|
1018
|
+
return {
|
|
1019
|
+
...extensionRegistry !== void 0 && { extensionRegistry },
|
|
1020
|
+
...fieldType !== void 0 && { fieldType }
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
968
1024
|
const name = classDecl.name?.text ?? "AnonymousClass";
|
|
969
1025
|
const fields = [];
|
|
970
1026
|
const fieldLayouts = [];
|
|
971
1027
|
const typeRegistry = {};
|
|
972
|
-
const annotations = extractJSDocAnnotationNodes(
|
|
1028
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1029
|
+
classDecl,
|
|
1030
|
+
file,
|
|
1031
|
+
makeParseOptions(extensionRegistry)
|
|
1032
|
+
);
|
|
973
1033
|
const visiting = /* @__PURE__ */ new Set();
|
|
974
1034
|
const instanceMethods = [];
|
|
975
1035
|
const staticMethods = [];
|
|
976
1036
|
for (const member of classDecl.members) {
|
|
977
|
-
if (
|
|
978
|
-
const fieldNode = analyzeFieldToIR(
|
|
1037
|
+
if (ts3.isPropertyDeclaration(member)) {
|
|
1038
|
+
const fieldNode = analyzeFieldToIR(
|
|
1039
|
+
member,
|
|
1040
|
+
checker,
|
|
1041
|
+
file,
|
|
1042
|
+
typeRegistry,
|
|
1043
|
+
visiting,
|
|
1044
|
+
extensionRegistry
|
|
1045
|
+
);
|
|
979
1046
|
if (fieldNode) {
|
|
980
1047
|
fields.push(fieldNode);
|
|
981
1048
|
fieldLayouts.push({});
|
|
982
1049
|
}
|
|
983
|
-
} else if (
|
|
1050
|
+
} else if (ts3.isMethodDeclaration(member)) {
|
|
984
1051
|
const methodInfo = analyzeMethod(member, checker);
|
|
985
1052
|
if (methodInfo) {
|
|
986
|
-
const isStatic = member.modifiers?.some((m) => m.kind ===
|
|
1053
|
+
const isStatic = member.modifiers?.some((m) => m.kind === ts3.SyntaxKind.StaticKeyword);
|
|
987
1054
|
if (isStatic) {
|
|
988
1055
|
staticMethods.push(methodInfo);
|
|
989
1056
|
} else {
|
|
@@ -1002,15 +1069,26 @@ function analyzeClassToIR(classDecl, checker, file = "") {
|
|
|
1002
1069
|
staticMethods
|
|
1003
1070
|
};
|
|
1004
1071
|
}
|
|
1005
|
-
function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
1072
|
+
function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
|
|
1006
1073
|
const name = interfaceDecl.name.text;
|
|
1007
1074
|
const fields = [];
|
|
1008
1075
|
const typeRegistry = {};
|
|
1009
|
-
const annotations = extractJSDocAnnotationNodes(
|
|
1076
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1077
|
+
interfaceDecl,
|
|
1078
|
+
file,
|
|
1079
|
+
makeParseOptions(extensionRegistry)
|
|
1080
|
+
);
|
|
1010
1081
|
const visiting = /* @__PURE__ */ new Set();
|
|
1011
1082
|
for (const member of interfaceDecl.members) {
|
|
1012
|
-
if (
|
|
1013
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1083
|
+
if (ts3.isPropertySignature(member)) {
|
|
1084
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1085
|
+
member,
|
|
1086
|
+
checker,
|
|
1087
|
+
file,
|
|
1088
|
+
typeRegistry,
|
|
1089
|
+
visiting,
|
|
1090
|
+
extensionRegistry
|
|
1091
|
+
);
|
|
1014
1092
|
if (fieldNode) {
|
|
1015
1093
|
fields.push(fieldNode);
|
|
1016
1094
|
}
|
|
@@ -1027,11 +1105,11 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
|
1027
1105
|
staticMethods: []
|
|
1028
1106
|
};
|
|
1029
1107
|
}
|
|
1030
|
-
function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
1031
|
-
if (!
|
|
1108
|
+
function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
|
|
1109
|
+
if (!ts3.isTypeLiteralNode(typeAlias.type)) {
|
|
1032
1110
|
const sourceFile = typeAlias.getSourceFile();
|
|
1033
1111
|
const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
|
|
1034
|
-
const kindDesc =
|
|
1112
|
+
const kindDesc = ts3.SyntaxKind[typeAlias.type.kind] ?? "unknown";
|
|
1035
1113
|
return {
|
|
1036
1114
|
ok: false,
|
|
1037
1115
|
error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
|
|
@@ -1040,11 +1118,22 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1040
1118
|
const name = typeAlias.name.text;
|
|
1041
1119
|
const fields = [];
|
|
1042
1120
|
const typeRegistry = {};
|
|
1043
|
-
const annotations = extractJSDocAnnotationNodes(
|
|
1121
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1122
|
+
typeAlias,
|
|
1123
|
+
file,
|
|
1124
|
+
makeParseOptions(extensionRegistry)
|
|
1125
|
+
);
|
|
1044
1126
|
const visiting = /* @__PURE__ */ new Set();
|
|
1045
1127
|
for (const member of typeAlias.type.members) {
|
|
1046
|
-
if (
|
|
1047
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1128
|
+
if (ts3.isPropertySignature(member)) {
|
|
1129
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1130
|
+
member,
|
|
1131
|
+
checker,
|
|
1132
|
+
file,
|
|
1133
|
+
typeRegistry,
|
|
1134
|
+
visiting,
|
|
1135
|
+
extensionRegistry
|
|
1136
|
+
);
|
|
1048
1137
|
if (fieldNode) {
|
|
1049
1138
|
fields.push(fieldNode);
|
|
1050
1139
|
}
|
|
@@ -1063,22 +1152,36 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1063
1152
|
}
|
|
1064
1153
|
};
|
|
1065
1154
|
}
|
|
1066
|
-
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
1067
|
-
if (!
|
|
1155
|
+
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1156
|
+
if (!ts3.isIdentifier(prop.name)) {
|
|
1068
1157
|
return null;
|
|
1069
1158
|
}
|
|
1070
1159
|
const name = prop.name.text;
|
|
1071
1160
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1072
1161
|
const optional = prop.questionToken !== void 0;
|
|
1073
1162
|
const provenance = provenanceForNode(prop, file);
|
|
1074
|
-
let type = resolveTypeNode(
|
|
1163
|
+
let type = resolveTypeNode(
|
|
1164
|
+
tsType,
|
|
1165
|
+
checker,
|
|
1166
|
+
file,
|
|
1167
|
+
typeRegistry,
|
|
1168
|
+
visiting,
|
|
1169
|
+
prop,
|
|
1170
|
+
extensionRegistry
|
|
1171
|
+
);
|
|
1075
1172
|
const constraints = [];
|
|
1076
|
-
if (prop.type) {
|
|
1077
|
-
constraints.push(
|
|
1173
|
+
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
1174
|
+
constraints.push(
|
|
1175
|
+
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
1176
|
+
);
|
|
1078
1177
|
}
|
|
1079
|
-
constraints.push(
|
|
1178
|
+
constraints.push(
|
|
1179
|
+
...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
1180
|
+
);
|
|
1080
1181
|
let annotations = [];
|
|
1081
|
-
annotations.push(
|
|
1182
|
+
annotations.push(
|
|
1183
|
+
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
1184
|
+
);
|
|
1082
1185
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
1083
1186
|
if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
|
|
1084
1187
|
annotations.push(defaultAnnotation);
|
|
@@ -1094,22 +1197,36 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
1094
1197
|
provenance
|
|
1095
1198
|
};
|
|
1096
1199
|
}
|
|
1097
|
-
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting) {
|
|
1098
|
-
if (!
|
|
1200
|
+
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1201
|
+
if (!ts3.isIdentifier(prop.name)) {
|
|
1099
1202
|
return null;
|
|
1100
1203
|
}
|
|
1101
1204
|
const name = prop.name.text;
|
|
1102
1205
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1103
1206
|
const optional = prop.questionToken !== void 0;
|
|
1104
1207
|
const provenance = provenanceForNode(prop, file);
|
|
1105
|
-
let type = resolveTypeNode(
|
|
1208
|
+
let type = resolveTypeNode(
|
|
1209
|
+
tsType,
|
|
1210
|
+
checker,
|
|
1211
|
+
file,
|
|
1212
|
+
typeRegistry,
|
|
1213
|
+
visiting,
|
|
1214
|
+
prop,
|
|
1215
|
+
extensionRegistry
|
|
1216
|
+
);
|
|
1106
1217
|
const constraints = [];
|
|
1107
|
-
if (prop.type) {
|
|
1108
|
-
constraints.push(
|
|
1218
|
+
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
1219
|
+
constraints.push(
|
|
1220
|
+
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
1221
|
+
);
|
|
1109
1222
|
}
|
|
1110
|
-
constraints.push(
|
|
1223
|
+
constraints.push(
|
|
1224
|
+
...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
1225
|
+
);
|
|
1111
1226
|
let annotations = [];
|
|
1112
|
-
annotations.push(
|
|
1227
|
+
annotations.push(
|
|
1228
|
+
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
1229
|
+
);
|
|
1113
1230
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
1114
1231
|
return {
|
|
1115
1232
|
kind: "field",
|
|
@@ -1183,20 +1300,94 @@ function parseEnumMemberDisplayName(value) {
|
|
|
1183
1300
|
if (label === "") return null;
|
|
1184
1301
|
return { value: match[1], label };
|
|
1185
1302
|
}
|
|
1186
|
-
function
|
|
1187
|
-
if (
|
|
1303
|
+
function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
|
|
1304
|
+
if (sourceNode === void 0 || extensionRegistry === void 0) {
|
|
1305
|
+
return null;
|
|
1306
|
+
}
|
|
1307
|
+
const typeNode = extractTypeNodeFromSource(sourceNode);
|
|
1308
|
+
if (typeNode === void 0) {
|
|
1309
|
+
return null;
|
|
1310
|
+
}
|
|
1311
|
+
return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
|
|
1312
|
+
}
|
|
1313
|
+
function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
|
|
1314
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
1315
|
+
return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
|
|
1316
|
+
}
|
|
1317
|
+
const typeName = getTypeNodeRegistrationName(typeNode);
|
|
1318
|
+
if (typeName === null) {
|
|
1319
|
+
return null;
|
|
1320
|
+
}
|
|
1321
|
+
const registration = extensionRegistry.findTypeByName(typeName);
|
|
1322
|
+
if (registration !== void 0) {
|
|
1323
|
+
return {
|
|
1324
|
+
kind: "custom",
|
|
1325
|
+
typeId: `${registration.extensionId}/${registration.registration.typeName}`,
|
|
1326
|
+
payload: null
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
if (ts3.isTypeReferenceNode(typeNode) && ts3.isIdentifier(typeNode.typeName)) {
|
|
1330
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
1331
|
+
if (aliasDecl !== void 0) {
|
|
1332
|
+
return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
return null;
|
|
1336
|
+
}
|
|
1337
|
+
function extractTypeNodeFromSource(sourceNode) {
|
|
1338
|
+
if (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode) || ts3.isTypeAliasDeclaration(sourceNode)) {
|
|
1339
|
+
return sourceNode.type;
|
|
1340
|
+
}
|
|
1341
|
+
if (ts3.isTypeNode(sourceNode)) {
|
|
1342
|
+
return sourceNode;
|
|
1343
|
+
}
|
|
1344
|
+
return void 0;
|
|
1345
|
+
}
|
|
1346
|
+
function getTypeNodeRegistrationName(typeNode) {
|
|
1347
|
+
if (ts3.isTypeReferenceNode(typeNode)) {
|
|
1348
|
+
return ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
|
|
1349
|
+
}
|
|
1350
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
1351
|
+
return getTypeNodeRegistrationName(typeNode.type);
|
|
1352
|
+
}
|
|
1353
|
+
if (typeNode.kind === ts3.SyntaxKind.BigIntKeyword || typeNode.kind === ts3.SyntaxKind.StringKeyword || typeNode.kind === ts3.SyntaxKind.NumberKeyword || typeNode.kind === ts3.SyntaxKind.BooleanKeyword) {
|
|
1354
|
+
return typeNode.getText();
|
|
1355
|
+
}
|
|
1356
|
+
return null;
|
|
1357
|
+
}
|
|
1358
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1359
|
+
const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
|
|
1360
|
+
if (customType) {
|
|
1361
|
+
return customType;
|
|
1362
|
+
}
|
|
1363
|
+
const primitiveAlias = tryResolveNamedPrimitiveAlias(
|
|
1364
|
+
type,
|
|
1365
|
+
checker,
|
|
1366
|
+
file,
|
|
1367
|
+
typeRegistry,
|
|
1368
|
+
visiting,
|
|
1369
|
+
sourceNode,
|
|
1370
|
+
extensionRegistry
|
|
1371
|
+
);
|
|
1372
|
+
if (primitiveAlias) {
|
|
1373
|
+
return primitiveAlias;
|
|
1374
|
+
}
|
|
1375
|
+
if (type.flags & ts3.TypeFlags.String) {
|
|
1188
1376
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1189
1377
|
}
|
|
1190
|
-
if (type.flags &
|
|
1378
|
+
if (type.flags & ts3.TypeFlags.Number) {
|
|
1191
1379
|
return { kind: "primitive", primitiveKind: "number" };
|
|
1192
1380
|
}
|
|
1193
|
-
if (type.flags &
|
|
1381
|
+
if (type.flags & (ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral)) {
|
|
1382
|
+
return { kind: "primitive", primitiveKind: "bigint" };
|
|
1383
|
+
}
|
|
1384
|
+
if (type.flags & ts3.TypeFlags.Boolean) {
|
|
1194
1385
|
return { kind: "primitive", primitiveKind: "boolean" };
|
|
1195
1386
|
}
|
|
1196
|
-
if (type.flags &
|
|
1387
|
+
if (type.flags & ts3.TypeFlags.Null) {
|
|
1197
1388
|
return { kind: "primitive", primitiveKind: "null" };
|
|
1198
1389
|
}
|
|
1199
|
-
if (type.flags &
|
|
1390
|
+
if (type.flags & ts3.TypeFlags.Undefined) {
|
|
1200
1391
|
return { kind: "primitive", primitiveKind: "null" };
|
|
1201
1392
|
}
|
|
1202
1393
|
if (type.isStringLiteral()) {
|
|
@@ -1212,27 +1403,120 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
1212
1403
|
};
|
|
1213
1404
|
}
|
|
1214
1405
|
if (type.isUnion()) {
|
|
1215
|
-
return resolveUnionType(
|
|
1406
|
+
return resolveUnionType(
|
|
1407
|
+
type,
|
|
1408
|
+
checker,
|
|
1409
|
+
file,
|
|
1410
|
+
typeRegistry,
|
|
1411
|
+
visiting,
|
|
1412
|
+
sourceNode,
|
|
1413
|
+
extensionRegistry
|
|
1414
|
+
);
|
|
1216
1415
|
}
|
|
1217
1416
|
if (checker.isArrayType(type)) {
|
|
1218
|
-
return resolveArrayType(
|
|
1417
|
+
return resolveArrayType(
|
|
1418
|
+
type,
|
|
1419
|
+
checker,
|
|
1420
|
+
file,
|
|
1421
|
+
typeRegistry,
|
|
1422
|
+
visiting,
|
|
1423
|
+
sourceNode,
|
|
1424
|
+
extensionRegistry
|
|
1425
|
+
);
|
|
1219
1426
|
}
|
|
1220
1427
|
if (isObjectType(type)) {
|
|
1221
|
-
return resolveObjectType(type, checker, file, typeRegistry, visiting);
|
|
1428
|
+
return resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry);
|
|
1222
1429
|
}
|
|
1223
1430
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1224
1431
|
}
|
|
1225
|
-
function
|
|
1432
|
+
function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1433
|
+
if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
|
|
1434
|
+
return null;
|
|
1435
|
+
}
|
|
1436
|
+
const aliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration) ?? getReferencedTypeAliasDeclaration(sourceNode, checker);
|
|
1437
|
+
if (!aliasDecl) {
|
|
1438
|
+
return null;
|
|
1439
|
+
}
|
|
1440
|
+
const aliasName = aliasDecl.name.text;
|
|
1441
|
+
if (!typeRegistry[aliasName]) {
|
|
1442
|
+
const aliasType = checker.getTypeFromTypeNode(aliasDecl.type);
|
|
1443
|
+
const constraints = [
|
|
1444
|
+
...extractJSDocConstraintNodes(aliasDecl, file, makeParseOptions(extensionRegistry)),
|
|
1445
|
+
...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry)
|
|
1446
|
+
];
|
|
1447
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1448
|
+
aliasDecl,
|
|
1449
|
+
file,
|
|
1450
|
+
makeParseOptions(extensionRegistry)
|
|
1451
|
+
);
|
|
1452
|
+
typeRegistry[aliasName] = {
|
|
1453
|
+
name: aliasName,
|
|
1454
|
+
type: resolveAliasedPrimitiveTarget(
|
|
1455
|
+
aliasType,
|
|
1456
|
+
checker,
|
|
1457
|
+
file,
|
|
1458
|
+
typeRegistry,
|
|
1459
|
+
visiting,
|
|
1460
|
+
extensionRegistry
|
|
1461
|
+
),
|
|
1462
|
+
...constraints.length > 0 && { constraints },
|
|
1463
|
+
...annotations.length > 0 && { annotations },
|
|
1464
|
+
provenance: provenanceForDeclaration(aliasDecl, file)
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
return { kind: "reference", name: aliasName, typeArguments: [] };
|
|
1468
|
+
}
|
|
1469
|
+
function getReferencedTypeAliasDeclaration(sourceNode, checker) {
|
|
1470
|
+
const typeNode = sourceNode && (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode)) ? sourceNode.type : void 0;
|
|
1471
|
+
if (!typeNode || !ts3.isTypeReferenceNode(typeNode)) {
|
|
1472
|
+
return void 0;
|
|
1473
|
+
}
|
|
1474
|
+
return checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
1475
|
+
}
|
|
1476
|
+
function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
|
|
1477
|
+
if (!ts3.isTypeReferenceNode(typeNode)) {
|
|
1478
|
+
return false;
|
|
1479
|
+
}
|
|
1480
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
1481
|
+
if (!aliasDecl) {
|
|
1482
|
+
return false;
|
|
1483
|
+
}
|
|
1484
|
+
const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
|
|
1485
|
+
return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
|
|
1486
|
+
}
|
|
1487
|
+
function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1488
|
+
const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
1489
|
+
if (nestedAliasDecl !== void 0) {
|
|
1490
|
+
return resolveAliasedPrimitiveTarget(
|
|
1491
|
+
checker.getTypeFromTypeNode(nestedAliasDecl.type),
|
|
1492
|
+
checker,
|
|
1493
|
+
file,
|
|
1494
|
+
typeRegistry,
|
|
1495
|
+
visiting,
|
|
1496
|
+
extensionRegistry
|
|
1497
|
+
);
|
|
1498
|
+
}
|
|
1499
|
+
return resolveTypeNode(type, checker, file, typeRegistry, visiting, void 0, extensionRegistry);
|
|
1500
|
+
}
|
|
1501
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1226
1502
|
const typeName = getNamedTypeName(type);
|
|
1227
1503
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
1228
1504
|
if (typeName && typeName in typeRegistry) {
|
|
1229
1505
|
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
1230
1506
|
}
|
|
1231
1507
|
const allTypes = type.types;
|
|
1508
|
+
const unionMemberTypeNodes = extractUnionMemberTypeNodes(sourceNode, checker);
|
|
1509
|
+
const nonNullSourceNodes = unionMemberTypeNodes.filter(
|
|
1510
|
+
(memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
|
|
1511
|
+
);
|
|
1232
1512
|
const nonNullTypes = allTypes.filter(
|
|
1233
|
-
(
|
|
1513
|
+
(memberType) => !(memberType.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
|
|
1234
1514
|
);
|
|
1235
|
-
const
|
|
1515
|
+
const nonNullMembers = nonNullTypes.map((memberType, index) => ({
|
|
1516
|
+
memberType,
|
|
1517
|
+
sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
|
|
1518
|
+
}));
|
|
1519
|
+
const hasNull = allTypes.some((t) => t.flags & ts3.TypeFlags.Null);
|
|
1236
1520
|
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1237
1521
|
if (namedDecl) {
|
|
1238
1522
|
for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
|
|
@@ -1248,7 +1532,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1248
1532
|
if (!typeName) {
|
|
1249
1533
|
return result;
|
|
1250
1534
|
}
|
|
1251
|
-
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
1535
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
1252
1536
|
typeRegistry[typeName] = {
|
|
1253
1537
|
name: typeName,
|
|
1254
1538
|
type: result,
|
|
@@ -1261,7 +1545,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1261
1545
|
const displayName = memberDisplayNames.get(String(value));
|
|
1262
1546
|
return displayName !== void 0 ? { value, displayName } : { value };
|
|
1263
1547
|
});
|
|
1264
|
-
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags &
|
|
1548
|
+
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts3.TypeFlags.BooleanLiteral);
|
|
1265
1549
|
if (isBooleanUnion2) {
|
|
1266
1550
|
const boolNode = { kind: "primitive", primitiveKind: "boolean" };
|
|
1267
1551
|
const result = hasNull ? {
|
|
@@ -1296,14 +1580,15 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1296
1580
|
} : enumNode;
|
|
1297
1581
|
return registerNamed(result);
|
|
1298
1582
|
}
|
|
1299
|
-
if (
|
|
1583
|
+
if (nonNullMembers.length === 1 && nonNullMembers[0]) {
|
|
1300
1584
|
const inner = resolveTypeNode(
|
|
1301
|
-
|
|
1585
|
+
nonNullMembers[0].memberType,
|
|
1302
1586
|
checker,
|
|
1303
1587
|
file,
|
|
1304
1588
|
typeRegistry,
|
|
1305
1589
|
visiting,
|
|
1306
|
-
sourceNode
|
|
1590
|
+
nonNullMembers[0].sourceNode ?? sourceNode,
|
|
1591
|
+
extensionRegistry
|
|
1307
1592
|
);
|
|
1308
1593
|
const result = hasNull ? {
|
|
1309
1594
|
kind: "union",
|
|
@@ -1311,29 +1596,54 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1311
1596
|
} : inner;
|
|
1312
1597
|
return registerNamed(result);
|
|
1313
1598
|
}
|
|
1314
|
-
const members =
|
|
1315
|
-
(
|
|
1599
|
+
const members = nonNullMembers.map(
|
|
1600
|
+
({ memberType, sourceNode: memberSourceNode }) => resolveTypeNode(
|
|
1601
|
+
memberType,
|
|
1602
|
+
checker,
|
|
1603
|
+
file,
|
|
1604
|
+
typeRegistry,
|
|
1605
|
+
visiting,
|
|
1606
|
+
memberSourceNode ?? sourceNode,
|
|
1607
|
+
extensionRegistry
|
|
1608
|
+
)
|
|
1316
1609
|
);
|
|
1317
1610
|
if (hasNull) {
|
|
1318
1611
|
members.push({ kind: "primitive", primitiveKind: "null" });
|
|
1319
1612
|
}
|
|
1320
1613
|
return registerNamed({ kind: "union", members });
|
|
1321
1614
|
}
|
|
1322
|
-
function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
1615
|
+
function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1323
1616
|
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
1324
1617
|
const elementType = typeArgs?.[0];
|
|
1325
|
-
const
|
|
1618
|
+
const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
|
|
1619
|
+
const items = elementType ? resolveTypeNode(
|
|
1620
|
+
elementType,
|
|
1621
|
+
checker,
|
|
1622
|
+
file,
|
|
1623
|
+
typeRegistry,
|
|
1624
|
+
visiting,
|
|
1625
|
+
elementSourceNode,
|
|
1626
|
+
extensionRegistry
|
|
1627
|
+
) : { kind: "primitive", primitiveKind: "string" };
|
|
1326
1628
|
return { kind: "array", items };
|
|
1327
1629
|
}
|
|
1328
|
-
function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
1630
|
+
function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1329
1631
|
if (type.getProperties().length > 0) {
|
|
1330
1632
|
return null;
|
|
1331
1633
|
}
|
|
1332
|
-
const indexInfo = checker.getIndexInfoOfType(type,
|
|
1634
|
+
const indexInfo = checker.getIndexInfoOfType(type, ts3.IndexKind.String);
|
|
1333
1635
|
if (!indexInfo) {
|
|
1334
1636
|
return null;
|
|
1335
1637
|
}
|
|
1336
|
-
const valueType = resolveTypeNode(
|
|
1638
|
+
const valueType = resolveTypeNode(
|
|
1639
|
+
indexInfo.type,
|
|
1640
|
+
checker,
|
|
1641
|
+
file,
|
|
1642
|
+
typeRegistry,
|
|
1643
|
+
visiting,
|
|
1644
|
+
void 0,
|
|
1645
|
+
extensionRegistry
|
|
1646
|
+
);
|
|
1337
1647
|
return { kind: "record", valueType };
|
|
1338
1648
|
}
|
|
1339
1649
|
function typeNodeContainsReference(type, targetName) {
|
|
@@ -1361,7 +1671,7 @@ function typeNodeContainsReference(type, targetName) {
|
|
|
1361
1671
|
}
|
|
1362
1672
|
}
|
|
1363
1673
|
}
|
|
1364
|
-
function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
1674
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1365
1675
|
const typeName = getNamedTypeName(type);
|
|
1366
1676
|
const namedTypeName = typeName ?? void 0;
|
|
1367
1677
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
@@ -1392,7 +1702,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1392
1702
|
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1393
1703
|
}
|
|
1394
1704
|
}
|
|
1395
|
-
const recordNode = tryResolveRecordType(
|
|
1705
|
+
const recordNode = tryResolveRecordType(
|
|
1706
|
+
type,
|
|
1707
|
+
checker,
|
|
1708
|
+
file,
|
|
1709
|
+
typeRegistry,
|
|
1710
|
+
visiting,
|
|
1711
|
+
extensionRegistry
|
|
1712
|
+
);
|
|
1396
1713
|
if (recordNode) {
|
|
1397
1714
|
visiting.delete(type);
|
|
1398
1715
|
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
@@ -1401,7 +1718,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1401
1718
|
clearNamedTypeRegistration();
|
|
1402
1719
|
return recordNode;
|
|
1403
1720
|
}
|
|
1404
|
-
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
1721
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
1405
1722
|
typeRegistry[namedTypeName] = {
|
|
1406
1723
|
name: namedTypeName,
|
|
1407
1724
|
type: recordNode,
|
|
@@ -1413,19 +1730,27 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1413
1730
|
return recordNode;
|
|
1414
1731
|
}
|
|
1415
1732
|
const properties = [];
|
|
1416
|
-
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
|
|
1733
|
+
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
|
|
1734
|
+
type,
|
|
1735
|
+
checker,
|
|
1736
|
+
file,
|
|
1737
|
+
typeRegistry,
|
|
1738
|
+
visiting,
|
|
1739
|
+
extensionRegistry
|
|
1740
|
+
);
|
|
1417
1741
|
for (const prop of type.getProperties()) {
|
|
1418
1742
|
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
1419
1743
|
if (!declaration) continue;
|
|
1420
1744
|
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
1421
|
-
const optional = !!(prop.flags &
|
|
1745
|
+
const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
|
|
1422
1746
|
const propTypeNode = resolveTypeNode(
|
|
1423
1747
|
propType,
|
|
1424
1748
|
checker,
|
|
1425
1749
|
file,
|
|
1426
1750
|
typeRegistry,
|
|
1427
1751
|
visiting,
|
|
1428
|
-
declaration
|
|
1752
|
+
declaration,
|
|
1753
|
+
extensionRegistry
|
|
1429
1754
|
);
|
|
1430
1755
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
1431
1756
|
properties.push({
|
|
@@ -1444,7 +1769,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1444
1769
|
additionalProperties: true
|
|
1445
1770
|
};
|
|
1446
1771
|
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
1447
|
-
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
1772
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
1448
1773
|
typeRegistry[namedTypeName] = {
|
|
1449
1774
|
name: namedTypeName,
|
|
1450
1775
|
type: objectNode,
|
|
@@ -1455,19 +1780,26 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1455
1780
|
}
|
|
1456
1781
|
return objectNode;
|
|
1457
1782
|
}
|
|
1458
|
-
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting) {
|
|
1783
|
+
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1459
1784
|
const symbols = [type.getSymbol(), type.aliasSymbol].filter(
|
|
1460
1785
|
(s) => s?.declarations != null && s.declarations.length > 0
|
|
1461
1786
|
);
|
|
1462
1787
|
for (const symbol of symbols) {
|
|
1463
1788
|
const declarations = symbol.declarations;
|
|
1464
1789
|
if (!declarations) continue;
|
|
1465
|
-
const classDecl = declarations.find(
|
|
1790
|
+
const classDecl = declarations.find(ts3.isClassDeclaration);
|
|
1466
1791
|
if (classDecl) {
|
|
1467
1792
|
const map = /* @__PURE__ */ new Map();
|
|
1468
1793
|
for (const member of classDecl.members) {
|
|
1469
|
-
if (
|
|
1470
|
-
const fieldNode = analyzeFieldToIR(
|
|
1794
|
+
if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name)) {
|
|
1795
|
+
const fieldNode = analyzeFieldToIR(
|
|
1796
|
+
member,
|
|
1797
|
+
checker,
|
|
1798
|
+
file,
|
|
1799
|
+
typeRegistry,
|
|
1800
|
+
visiting,
|
|
1801
|
+
extensionRegistry
|
|
1802
|
+
);
|
|
1471
1803
|
if (fieldNode) {
|
|
1472
1804
|
map.set(fieldNode.name, {
|
|
1473
1805
|
constraints: [...fieldNode.constraints],
|
|
@@ -1479,28 +1811,86 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1479
1811
|
}
|
|
1480
1812
|
return map;
|
|
1481
1813
|
}
|
|
1482
|
-
const interfaceDecl = declarations.find(
|
|
1814
|
+
const interfaceDecl = declarations.find(ts3.isInterfaceDeclaration);
|
|
1483
1815
|
if (interfaceDecl) {
|
|
1484
|
-
return buildFieldNodeInfoMap(
|
|
1816
|
+
return buildFieldNodeInfoMap(
|
|
1817
|
+
interfaceDecl.members,
|
|
1818
|
+
checker,
|
|
1819
|
+
file,
|
|
1820
|
+
typeRegistry,
|
|
1821
|
+
visiting,
|
|
1822
|
+
extensionRegistry
|
|
1823
|
+
);
|
|
1485
1824
|
}
|
|
1486
|
-
const typeAliasDecl = declarations.find(
|
|
1487
|
-
if (typeAliasDecl &&
|
|
1825
|
+
const typeAliasDecl = declarations.find(ts3.isTypeAliasDeclaration);
|
|
1826
|
+
if (typeAliasDecl && ts3.isTypeLiteralNode(typeAliasDecl.type)) {
|
|
1488
1827
|
return buildFieldNodeInfoMap(
|
|
1489
1828
|
typeAliasDecl.type.members,
|
|
1490
1829
|
checker,
|
|
1491
1830
|
file,
|
|
1492
1831
|
typeRegistry,
|
|
1493
|
-
visiting
|
|
1832
|
+
visiting,
|
|
1833
|
+
extensionRegistry
|
|
1494
1834
|
);
|
|
1495
1835
|
}
|
|
1496
1836
|
}
|
|
1497
1837
|
return null;
|
|
1498
1838
|
}
|
|
1499
|
-
function
|
|
1839
|
+
function extractArrayElementTypeNode(sourceNode, checker) {
|
|
1840
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
1841
|
+
if (typeNode === void 0) {
|
|
1842
|
+
return void 0;
|
|
1843
|
+
}
|
|
1844
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
1845
|
+
if (ts3.isArrayTypeNode(resolvedTypeNode)) {
|
|
1846
|
+
return resolvedTypeNode.elementType;
|
|
1847
|
+
}
|
|
1848
|
+
if (ts3.isTypeReferenceNode(resolvedTypeNode) && ts3.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
|
|
1849
|
+
return resolvedTypeNode.typeArguments[0];
|
|
1850
|
+
}
|
|
1851
|
+
return void 0;
|
|
1852
|
+
}
|
|
1853
|
+
function extractUnionMemberTypeNodes(sourceNode, checker) {
|
|
1854
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
1855
|
+
if (!typeNode) {
|
|
1856
|
+
return [];
|
|
1857
|
+
}
|
|
1858
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
1859
|
+
return ts3.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
|
|
1860
|
+
}
|
|
1861
|
+
function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
|
|
1862
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
1863
|
+
return resolveAliasedTypeNode(typeNode.type, checker, visited);
|
|
1864
|
+
}
|
|
1865
|
+
if (!ts3.isTypeReferenceNode(typeNode) || !ts3.isIdentifier(typeNode.typeName)) {
|
|
1866
|
+
return typeNode;
|
|
1867
|
+
}
|
|
1868
|
+
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
1869
|
+
const aliasDecl = symbol?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
1870
|
+
if (aliasDecl === void 0 || visited.has(aliasDecl)) {
|
|
1871
|
+
return typeNode;
|
|
1872
|
+
}
|
|
1873
|
+
visited.add(aliasDecl);
|
|
1874
|
+
return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
|
|
1875
|
+
}
|
|
1876
|
+
function isNullishTypeNode(typeNode) {
|
|
1877
|
+
if (typeNode.kind === ts3.SyntaxKind.NullKeyword || typeNode.kind === ts3.SyntaxKind.UndefinedKeyword) {
|
|
1878
|
+
return true;
|
|
1879
|
+
}
|
|
1880
|
+
return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
|
|
1881
|
+
}
|
|
1882
|
+
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1500
1883
|
const map = /* @__PURE__ */ new Map();
|
|
1501
1884
|
for (const member of members) {
|
|
1502
|
-
if (
|
|
1503
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1885
|
+
if (ts3.isPropertySignature(member)) {
|
|
1886
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1887
|
+
member,
|
|
1888
|
+
checker,
|
|
1889
|
+
file,
|
|
1890
|
+
typeRegistry,
|
|
1891
|
+
visiting,
|
|
1892
|
+
extensionRegistry
|
|
1893
|
+
);
|
|
1504
1894
|
if (fieldNode) {
|
|
1505
1895
|
map.set(fieldNode.name, {
|
|
1506
1896
|
constraints: [...fieldNode.constraints],
|
|
@@ -1513,8 +1903,8 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
|
1513
1903
|
return map;
|
|
1514
1904
|
}
|
|
1515
1905
|
var MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
1516
|
-
function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
1517
|
-
if (!
|
|
1906
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
|
|
1907
|
+
if (!ts3.isTypeReferenceNode(typeNode)) return [];
|
|
1518
1908
|
if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
|
|
1519
1909
|
const aliasName = typeNode.typeName.getText();
|
|
1520
1910
|
throw new Error(
|
|
@@ -1523,11 +1913,26 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
|
1523
1913
|
}
|
|
1524
1914
|
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
1525
1915
|
if (!symbol?.declarations) return [];
|
|
1526
|
-
const aliasDecl = symbol.declarations.find(
|
|
1916
|
+
const aliasDecl = symbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
1527
1917
|
if (!aliasDecl) return [];
|
|
1528
|
-
if (
|
|
1529
|
-
const
|
|
1530
|
-
|
|
1918
|
+
if (ts3.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
1919
|
+
const aliasFieldType = resolveTypeNode(
|
|
1920
|
+
checker.getTypeAtLocation(aliasDecl.type),
|
|
1921
|
+
checker,
|
|
1922
|
+
file,
|
|
1923
|
+
{},
|
|
1924
|
+
/* @__PURE__ */ new Set(),
|
|
1925
|
+
aliasDecl.type,
|
|
1926
|
+
extensionRegistry
|
|
1927
|
+
);
|
|
1928
|
+
const constraints = extractJSDocConstraintNodes(
|
|
1929
|
+
aliasDecl,
|
|
1930
|
+
file,
|
|
1931
|
+
makeParseOptions(extensionRegistry, aliasFieldType)
|
|
1932
|
+
);
|
|
1933
|
+
constraints.push(
|
|
1934
|
+
...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry, depth + 1)
|
|
1935
|
+
);
|
|
1531
1936
|
return constraints;
|
|
1532
1937
|
}
|
|
1533
1938
|
function provenanceForNode(node, file) {
|
|
@@ -1553,14 +1958,14 @@ function getNamedTypeName(type) {
|
|
|
1553
1958
|
const symbol = type.getSymbol();
|
|
1554
1959
|
if (symbol?.declarations) {
|
|
1555
1960
|
const decl = symbol.declarations[0];
|
|
1556
|
-
if (decl && (
|
|
1557
|
-
const name =
|
|
1961
|
+
if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
|
|
1962
|
+
const name = ts3.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
|
|
1558
1963
|
if (name) return name;
|
|
1559
1964
|
}
|
|
1560
1965
|
}
|
|
1561
1966
|
const aliasSymbol = type.aliasSymbol;
|
|
1562
1967
|
if (aliasSymbol?.declarations) {
|
|
1563
|
-
const aliasDecl = aliasSymbol.declarations.find(
|
|
1968
|
+
const aliasDecl = aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
1564
1969
|
if (aliasDecl) {
|
|
1565
1970
|
return aliasDecl.name.text;
|
|
1566
1971
|
}
|
|
@@ -1571,24 +1976,24 @@ function getNamedTypeDeclaration(type) {
|
|
|
1571
1976
|
const symbol = type.getSymbol();
|
|
1572
1977
|
if (symbol?.declarations) {
|
|
1573
1978
|
const decl = symbol.declarations[0];
|
|
1574
|
-
if (decl && (
|
|
1979
|
+
if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
|
|
1575
1980
|
return decl;
|
|
1576
1981
|
}
|
|
1577
1982
|
}
|
|
1578
1983
|
const aliasSymbol = type.aliasSymbol;
|
|
1579
1984
|
if (aliasSymbol?.declarations) {
|
|
1580
|
-
return aliasSymbol.declarations.find(
|
|
1985
|
+
return aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
1581
1986
|
}
|
|
1582
1987
|
return void 0;
|
|
1583
1988
|
}
|
|
1584
1989
|
function analyzeMethod(method, checker) {
|
|
1585
|
-
if (!
|
|
1990
|
+
if (!ts3.isIdentifier(method.name)) {
|
|
1586
1991
|
return null;
|
|
1587
1992
|
}
|
|
1588
1993
|
const name = method.name.text;
|
|
1589
1994
|
const parameters = [];
|
|
1590
1995
|
for (const param of method.parameters) {
|
|
1591
|
-
if (
|
|
1996
|
+
if (ts3.isIdentifier(param.name)) {
|
|
1592
1997
|
const paramInfo = analyzeParameter(param, checker);
|
|
1593
1998
|
parameters.push(paramInfo);
|
|
1594
1999
|
}
|
|
@@ -1599,7 +2004,7 @@ function analyzeMethod(method, checker) {
|
|
|
1599
2004
|
return { name, parameters, returnTypeNode, returnType };
|
|
1600
2005
|
}
|
|
1601
2006
|
function analyzeParameter(param, checker) {
|
|
1602
|
-
const name =
|
|
2007
|
+
const name = ts3.isIdentifier(param.name) ? param.name.text : "param";
|
|
1603
2008
|
const typeNode = param.type;
|
|
1604
2009
|
const type = checker.getTypeAtLocation(param);
|
|
1605
2010
|
const formSpecExportName = detectFormSpecReference(typeNode);
|
|
@@ -1608,20 +2013,90 @@ function analyzeParameter(param, checker) {
|
|
|
1608
2013
|
}
|
|
1609
2014
|
function detectFormSpecReference(typeNode) {
|
|
1610
2015
|
if (!typeNode) return null;
|
|
1611
|
-
if (!
|
|
1612
|
-
const typeName =
|
|
2016
|
+
if (!ts3.isTypeReferenceNode(typeNode)) return null;
|
|
2017
|
+
const typeName = ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts3.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
|
|
1613
2018
|
if (typeName !== "InferSchema" && typeName !== "InferFormSchema") return null;
|
|
1614
2019
|
const typeArg = typeNode.typeArguments?.[0];
|
|
1615
|
-
if (!typeArg || !
|
|
1616
|
-
if (
|
|
2020
|
+
if (!typeArg || !ts3.isTypeQueryNode(typeArg)) return null;
|
|
2021
|
+
if (ts3.isIdentifier(typeArg.exprName)) {
|
|
1617
2022
|
return typeArg.exprName.text;
|
|
1618
2023
|
}
|
|
1619
|
-
if (
|
|
2024
|
+
if (ts3.isQualifiedName(typeArg.exprName)) {
|
|
1620
2025
|
return typeArg.exprName.right.text;
|
|
1621
2026
|
}
|
|
1622
2027
|
return null;
|
|
1623
2028
|
}
|
|
1624
2029
|
|
|
2030
|
+
// src/analyzer/program.ts
|
|
2031
|
+
function createProgramContext(filePath) {
|
|
2032
|
+
const absolutePath = path.resolve(filePath);
|
|
2033
|
+
const fileDir = path.dirname(absolutePath);
|
|
2034
|
+
const configPath = ts4.findConfigFile(fileDir, ts4.sys.fileExists.bind(ts4.sys), "tsconfig.json");
|
|
2035
|
+
let compilerOptions;
|
|
2036
|
+
let fileNames;
|
|
2037
|
+
if (configPath) {
|
|
2038
|
+
const configFile = ts4.readConfigFile(configPath, ts4.sys.readFile.bind(ts4.sys));
|
|
2039
|
+
if (configFile.error) {
|
|
2040
|
+
throw new Error(
|
|
2041
|
+
`Error reading tsconfig.json: ${ts4.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
|
|
2042
|
+
);
|
|
2043
|
+
}
|
|
2044
|
+
const parsed = ts4.parseJsonConfigFileContent(
|
|
2045
|
+
configFile.config,
|
|
2046
|
+
ts4.sys,
|
|
2047
|
+
path.dirname(configPath)
|
|
2048
|
+
);
|
|
2049
|
+
if (parsed.errors.length > 0) {
|
|
2050
|
+
const errorMessages = parsed.errors.map((e) => ts4.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
|
|
2051
|
+
throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
|
|
2052
|
+
}
|
|
2053
|
+
compilerOptions = parsed.options;
|
|
2054
|
+
fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
|
|
2055
|
+
} else {
|
|
2056
|
+
compilerOptions = {
|
|
2057
|
+
target: ts4.ScriptTarget.ES2022,
|
|
2058
|
+
module: ts4.ModuleKind.NodeNext,
|
|
2059
|
+
moduleResolution: ts4.ModuleResolutionKind.NodeNext,
|
|
2060
|
+
strict: true,
|
|
2061
|
+
skipLibCheck: true,
|
|
2062
|
+
declaration: true
|
|
2063
|
+
};
|
|
2064
|
+
fileNames = [absolutePath];
|
|
2065
|
+
}
|
|
2066
|
+
const program = ts4.createProgram(fileNames, compilerOptions);
|
|
2067
|
+
const sourceFile = program.getSourceFile(absolutePath);
|
|
2068
|
+
if (!sourceFile) {
|
|
2069
|
+
throw new Error(`Could not find source file: ${absolutePath}`);
|
|
2070
|
+
}
|
|
2071
|
+
return {
|
|
2072
|
+
program,
|
|
2073
|
+
checker: program.getTypeChecker(),
|
|
2074
|
+
sourceFile
|
|
2075
|
+
};
|
|
2076
|
+
}
|
|
2077
|
+
function findNodeByName(sourceFile, name, predicate, getName) {
|
|
2078
|
+
let result = null;
|
|
2079
|
+
function visit(node) {
|
|
2080
|
+
if (result) return;
|
|
2081
|
+
if (predicate(node) && getName(node) === name) {
|
|
2082
|
+
result = node;
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
ts4.forEachChild(node, visit);
|
|
2086
|
+
}
|
|
2087
|
+
visit(sourceFile);
|
|
2088
|
+
return result;
|
|
2089
|
+
}
|
|
2090
|
+
function findClassByName(sourceFile, className) {
|
|
2091
|
+
return findNodeByName(sourceFile, className, ts4.isClassDeclaration, (n) => n.name?.text);
|
|
2092
|
+
}
|
|
2093
|
+
function findInterfaceByName(sourceFile, interfaceName) {
|
|
2094
|
+
return findNodeByName(sourceFile, interfaceName, ts4.isInterfaceDeclaration, (n) => n.name.text);
|
|
2095
|
+
}
|
|
2096
|
+
function findTypeAliasByName(sourceFile, aliasName) {
|
|
2097
|
+
return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
|
|
2098
|
+
}
|
|
2099
|
+
|
|
1625
2100
|
// src/json-schema/ir-generator.ts
|
|
1626
2101
|
function makeContext(options) {
|
|
1627
2102
|
const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
|
|
@@ -1640,6 +2115,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
1640
2115
|
const ctx = makeContext(options);
|
|
1641
2116
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
1642
2117
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
2118
|
+
if (typeDef.constraints && typeDef.constraints.length > 0) {
|
|
2119
|
+
applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
|
|
2120
|
+
}
|
|
1643
2121
|
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
1644
2122
|
applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
|
|
1645
2123
|
}
|
|
@@ -1808,7 +2286,9 @@ function generateTypeNode(type, ctx) {
|
|
|
1808
2286
|
}
|
|
1809
2287
|
}
|
|
1810
2288
|
function generatePrimitiveType(type) {
|
|
1811
|
-
return {
|
|
2289
|
+
return {
|
|
2290
|
+
type: type.primitiveKind === "integer" || type.primitiveKind === "bigint" ? "integer" : type.primitiveKind
|
|
2291
|
+
};
|
|
1812
2292
|
}
|
|
1813
2293
|
function generateEnumType(type) {
|
|
1814
2294
|
const hasDisplayNames = type.members.some((m) => m.displayName !== void 0);
|
|
@@ -1981,7 +2461,7 @@ function applyAnnotations(schema, annotations, ctx) {
|
|
|
1981
2461
|
case "deprecated":
|
|
1982
2462
|
schema.deprecated = true;
|
|
1983
2463
|
if (annotation.message !== void 0 && annotation.message !== "") {
|
|
1984
|
-
schema[
|
|
2464
|
+
schema[`${ctx.vendorPrefix}-deprecation-description`] = annotation.message;
|
|
1985
2465
|
}
|
|
1986
2466
|
break;
|
|
1987
2467
|
case "placeholder":
|
|
@@ -2014,7 +2494,12 @@ function applyCustomConstraint(schema, constraint, ctx) {
|
|
|
2014
2494
|
`Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
|
|
2015
2495
|
);
|
|
2016
2496
|
}
|
|
2017
|
-
|
|
2497
|
+
assignVendorPrefixedExtensionKeywords(
|
|
2498
|
+
schema,
|
|
2499
|
+
registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
|
|
2500
|
+
ctx.vendorPrefix,
|
|
2501
|
+
`custom constraint "${constraint.constraintId}"`
|
|
2502
|
+
);
|
|
2018
2503
|
}
|
|
2019
2504
|
function applyCustomAnnotation(schema, annotation, ctx) {
|
|
2020
2505
|
const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
|
|
@@ -2026,7 +2511,22 @@ function applyCustomAnnotation(schema, annotation, ctx) {
|
|
|
2026
2511
|
if (registration.toJsonSchema === void 0) {
|
|
2027
2512
|
return;
|
|
2028
2513
|
}
|
|
2029
|
-
|
|
2514
|
+
assignVendorPrefixedExtensionKeywords(
|
|
2515
|
+
schema,
|
|
2516
|
+
registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
|
|
2517
|
+
ctx.vendorPrefix,
|
|
2518
|
+
`custom annotation "${annotation.annotationId}"`
|
|
2519
|
+
);
|
|
2520
|
+
}
|
|
2521
|
+
function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
|
|
2522
|
+
for (const [key, value] of Object.entries(extensionSchema)) {
|
|
2523
|
+
if (!key.startsWith(`${vendorPrefix}-`)) {
|
|
2524
|
+
throw new Error(
|
|
2525
|
+
`Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
|
|
2526
|
+
);
|
|
2527
|
+
}
|
|
2528
|
+
schema[key] = value;
|
|
2529
|
+
}
|
|
2030
2530
|
}
|
|
2031
2531
|
|
|
2032
2532
|
// src/ui-schema/schema.ts
|
|
@@ -2252,16 +2752,8 @@ function generateUiSchemaFromIR(ir) {
|
|
|
2252
2752
|
return parseOrThrow(uiSchema, result, "UI Schema");
|
|
2253
2753
|
}
|
|
2254
2754
|
|
|
2255
|
-
// src/generators/class-schema.ts
|
|
2256
|
-
function generateClassSchemas(analysis, source) {
|
|
2257
|
-
const ir = canonicalizeTSDoc(analysis, source);
|
|
2258
|
-
return {
|
|
2259
|
-
jsonSchema: generateJsonSchemaFromIR(ir),
|
|
2260
|
-
uiSchema: generateUiSchemaFromIR(ir)
|
|
2261
|
-
};
|
|
2262
|
-
}
|
|
2263
|
-
|
|
2264
2755
|
// src/validate/constraint-validator.ts
|
|
2756
|
+
var import_core4 = require("@formspec/core");
|
|
2265
2757
|
function addContradiction(ctx, message, primary, related) {
|
|
2266
2758
|
ctx.diagnostics.push({
|
|
2267
2759
|
code: "CONTRADICTING_CONSTRAINTS",
|
|
@@ -2307,6 +2799,13 @@ function addConstraintBroadening(ctx, message, primary, related) {
|
|
|
2307
2799
|
relatedLocations: [related]
|
|
2308
2800
|
});
|
|
2309
2801
|
}
|
|
2802
|
+
function getExtensionIdFromConstraintId(constraintId) {
|
|
2803
|
+
const separator = constraintId.lastIndexOf("/");
|
|
2804
|
+
if (separator <= 0) {
|
|
2805
|
+
return null;
|
|
2806
|
+
}
|
|
2807
|
+
return constraintId.slice(0, separator);
|
|
2808
|
+
}
|
|
2310
2809
|
function findNumeric(constraints, constraintKind) {
|
|
2311
2810
|
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
2312
2811
|
}
|
|
@@ -2477,6 +2976,112 @@ function checkConstraintBroadening(ctx, fieldName, constraints) {
|
|
|
2477
2976
|
strongestByKey.set(key, constraint);
|
|
2478
2977
|
}
|
|
2479
2978
|
}
|
|
2979
|
+
function compareCustomConstraintStrength(current, previous) {
|
|
2980
|
+
const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
|
|
2981
|
+
const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
|
|
2982
|
+
switch (current.role.bound) {
|
|
2983
|
+
case "lower":
|
|
2984
|
+
return equalPayloadTiebreaker;
|
|
2985
|
+
case "upper":
|
|
2986
|
+
return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
|
|
2987
|
+
case "exact":
|
|
2988
|
+
return order === 0 ? 0 : Number.NaN;
|
|
2989
|
+
default: {
|
|
2990
|
+
const _exhaustive = current.role.bound;
|
|
2991
|
+
return _exhaustive;
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
function compareSemanticInclusivity(currentInclusive, previousInclusive) {
|
|
2996
|
+
if (currentInclusive === previousInclusive) {
|
|
2997
|
+
return 0;
|
|
2998
|
+
}
|
|
2999
|
+
return currentInclusive ? -1 : 1;
|
|
3000
|
+
}
|
|
3001
|
+
function customConstraintsContradict(lower, upper) {
|
|
3002
|
+
const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
|
|
3003
|
+
if (order > 0) {
|
|
3004
|
+
return true;
|
|
3005
|
+
}
|
|
3006
|
+
if (order < 0) {
|
|
3007
|
+
return false;
|
|
3008
|
+
}
|
|
3009
|
+
return !lower.role.inclusive || !upper.role.inclusive;
|
|
3010
|
+
}
|
|
3011
|
+
function describeCustomConstraintTag(constraint) {
|
|
3012
|
+
return constraint.provenance.tagName ?? constraint.constraintId;
|
|
3013
|
+
}
|
|
3014
|
+
function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
|
|
3015
|
+
if (ctx.extensionRegistry === void 0) {
|
|
3016
|
+
return;
|
|
3017
|
+
}
|
|
3018
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
3019
|
+
const lowerByFamily = /* @__PURE__ */ new Map();
|
|
3020
|
+
const upperByFamily = /* @__PURE__ */ new Map();
|
|
3021
|
+
for (const constraint of constraints) {
|
|
3022
|
+
if (constraint.constraintKind !== "custom") {
|
|
3023
|
+
continue;
|
|
3024
|
+
}
|
|
3025
|
+
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
3026
|
+
if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
|
|
3027
|
+
continue;
|
|
3028
|
+
}
|
|
3029
|
+
const entry = {
|
|
3030
|
+
constraint,
|
|
3031
|
+
comparePayloads: registration.comparePayloads,
|
|
3032
|
+
role: registration.semanticRole
|
|
3033
|
+
};
|
|
3034
|
+
const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
|
|
3035
|
+
const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
|
|
3036
|
+
const previous = strongestByKey.get(boundKey);
|
|
3037
|
+
if (previous !== void 0) {
|
|
3038
|
+
const strength = compareCustomConstraintStrength(entry, previous);
|
|
3039
|
+
if (Number.isNaN(strength)) {
|
|
3040
|
+
addContradiction(
|
|
3041
|
+
ctx,
|
|
3042
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
|
|
3043
|
+
constraint.provenance,
|
|
3044
|
+
previous.constraint.provenance
|
|
3045
|
+
);
|
|
3046
|
+
continue;
|
|
3047
|
+
}
|
|
3048
|
+
if (strength < 0) {
|
|
3049
|
+
addConstraintBroadening(
|
|
3050
|
+
ctx,
|
|
3051
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
|
|
3052
|
+
constraint.provenance,
|
|
3053
|
+
previous.constraint.provenance
|
|
3054
|
+
);
|
|
3055
|
+
continue;
|
|
3056
|
+
}
|
|
3057
|
+
if (strength > 0) {
|
|
3058
|
+
strongestByKey.set(boundKey, entry);
|
|
3059
|
+
}
|
|
3060
|
+
} else {
|
|
3061
|
+
strongestByKey.set(boundKey, entry);
|
|
3062
|
+
}
|
|
3063
|
+
if (registration.semanticRole.bound === "lower") {
|
|
3064
|
+
lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
3065
|
+
} else if (registration.semanticRole.bound === "upper") {
|
|
3066
|
+
upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
for (const [familyKey, lower] of lowerByFamily) {
|
|
3070
|
+
const upper = upperByFamily.get(familyKey);
|
|
3071
|
+
if (upper === void 0) {
|
|
3072
|
+
continue;
|
|
3073
|
+
}
|
|
3074
|
+
if (!customConstraintsContradict(lower, upper)) {
|
|
3075
|
+
continue;
|
|
3076
|
+
}
|
|
3077
|
+
addContradiction(
|
|
3078
|
+
ctx,
|
|
3079
|
+
`Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
|
|
3080
|
+
lower.constraint.provenance,
|
|
3081
|
+
upper.constraint.provenance
|
|
3082
|
+
);
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
2480
3085
|
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
2481
3086
|
const min = findNumeric(constraints, "minimum");
|
|
2482
3087
|
const max = findNumeric(constraints, "maximum");
|
|
@@ -2624,6 +3229,26 @@ function dereferenceType(ctx, type) {
|
|
|
2624
3229
|
}
|
|
2625
3230
|
return current;
|
|
2626
3231
|
}
|
|
3232
|
+
function collectReferencedTypeConstraints(ctx, type) {
|
|
3233
|
+
const collected = [];
|
|
3234
|
+
let current = type;
|
|
3235
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3236
|
+
while (current.kind === "reference") {
|
|
3237
|
+
if (seen.has(current.name)) {
|
|
3238
|
+
break;
|
|
3239
|
+
}
|
|
3240
|
+
seen.add(current.name);
|
|
3241
|
+
const definition = ctx.typeRegistry[current.name];
|
|
3242
|
+
if (definition === void 0) {
|
|
3243
|
+
break;
|
|
3244
|
+
}
|
|
3245
|
+
if (definition.constraints !== void 0) {
|
|
3246
|
+
collected.push(...definition.constraints);
|
|
3247
|
+
}
|
|
3248
|
+
current = definition.type;
|
|
3249
|
+
}
|
|
3250
|
+
return collected;
|
|
3251
|
+
}
|
|
2627
3252
|
function resolvePathTargetType(ctx, type, segments) {
|
|
2628
3253
|
const effectiveType = dereferenceType(ctx, type);
|
|
2629
3254
|
if (segments.length === 0) {
|
|
@@ -2645,12 +3270,33 @@ function resolvePathTargetType(ctx, type, segments) {
|
|
|
2645
3270
|
}
|
|
2646
3271
|
return { kind: "unresolvable", type: effectiveType };
|
|
2647
3272
|
}
|
|
3273
|
+
function isNullType(type) {
|
|
3274
|
+
return type.kind === "primitive" && type.primitiveKind === "null";
|
|
3275
|
+
}
|
|
3276
|
+
function collectCustomConstraintCandidateTypes(ctx, type) {
|
|
3277
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
3278
|
+
const candidates = [effectiveType];
|
|
3279
|
+
if (effectiveType.kind === "array") {
|
|
3280
|
+
candidates.push(...collectCustomConstraintCandidateTypes(ctx, effectiveType.items));
|
|
3281
|
+
}
|
|
3282
|
+
if (effectiveType.kind === "union") {
|
|
3283
|
+
const memberTypes = effectiveType.members.map((member) => dereferenceType(ctx, member));
|
|
3284
|
+
const nonNullMembers = memberTypes.filter((member) => !isNullType(member));
|
|
3285
|
+
if (nonNullMembers.length === 1 && nonNullMembers.length < memberTypes.length) {
|
|
3286
|
+
const [nullableMember] = nonNullMembers;
|
|
3287
|
+
if (nullableMember !== void 0) {
|
|
3288
|
+
candidates.push(...collectCustomConstraintCandidateTypes(ctx, nullableMember));
|
|
3289
|
+
}
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
return candidates;
|
|
3293
|
+
}
|
|
2648
3294
|
function formatPathTargetFieldName(fieldName, path2) {
|
|
2649
3295
|
return path2.length === 0 ? fieldName : `${fieldName}.${path2.join(".")}`;
|
|
2650
3296
|
}
|
|
2651
3297
|
function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
2652
3298
|
const effectiveType = dereferenceType(ctx, type);
|
|
2653
|
-
const isNumber = effectiveType.kind === "primitive" &&
|
|
3299
|
+
const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
|
|
2654
3300
|
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
2655
3301
|
const isArray = effectiveType.kind === "array";
|
|
2656
3302
|
const isEnum = effectiveType.kind === "enum";
|
|
@@ -2708,7 +3354,9 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
|
2708
3354
|
break;
|
|
2709
3355
|
}
|
|
2710
3356
|
case "const": {
|
|
2711
|
-
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "boolean", "null"].includes(
|
|
3357
|
+
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
|
|
3358
|
+
effectiveType.primitiveKind
|
|
3359
|
+
) || effectiveType.kind === "enum";
|
|
2712
3360
|
if (!isPrimitiveConstType) {
|
|
2713
3361
|
addTypeMismatch(
|
|
2714
3362
|
ctx,
|
|
@@ -2719,7 +3367,8 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
|
2719
3367
|
}
|
|
2720
3368
|
if (effectiveType.kind === "primitive") {
|
|
2721
3369
|
const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
|
|
2722
|
-
|
|
3370
|
+
const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
|
|
3371
|
+
if (valueType !== expectedValueType) {
|
|
2723
3372
|
addTypeMismatch(
|
|
2724
3373
|
ctx,
|
|
2725
3374
|
`Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
|
|
@@ -2788,8 +3437,37 @@ function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
|
2788
3437
|
);
|
|
2789
3438
|
return;
|
|
2790
3439
|
}
|
|
2791
|
-
|
|
2792
|
-
|
|
3440
|
+
const candidateTypes = collectCustomConstraintCandidateTypes(ctx, type);
|
|
3441
|
+
const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : (0, import_core4.normalizeConstraintTagName)(constraint.provenance.tagName.replace(/^@/, ""));
|
|
3442
|
+
if (normalizedTagName !== void 0) {
|
|
3443
|
+
const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
|
|
3444
|
+
const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
|
|
3445
|
+
if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && !candidateTypes.some(
|
|
3446
|
+
(candidateType) => tagRegistration.registration.isApplicableToType?.(candidateType) !== false
|
|
3447
|
+
)) {
|
|
3448
|
+
addTypeMismatch(
|
|
3449
|
+
ctx,
|
|
3450
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3451
|
+
constraint.provenance
|
|
3452
|
+
);
|
|
3453
|
+
return;
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
if (registration.applicableTypes === null) {
|
|
3457
|
+
if (!candidateTypes.some((candidateType) => registration.isApplicableToType?.(candidateType) !== false)) {
|
|
3458
|
+
addTypeMismatch(
|
|
3459
|
+
ctx,
|
|
3460
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3461
|
+
constraint.provenance
|
|
3462
|
+
);
|
|
3463
|
+
}
|
|
3464
|
+
return;
|
|
3465
|
+
}
|
|
3466
|
+
const applicableTypes = registration.applicableTypes;
|
|
3467
|
+
const matchesApplicableType = candidateTypes.some(
|
|
3468
|
+
(candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
|
|
3469
|
+
);
|
|
3470
|
+
if (!matchesApplicableType) {
|
|
2793
3471
|
addTypeMismatch(
|
|
2794
3472
|
ctx,
|
|
2795
3473
|
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
@@ -2798,7 +3476,10 @@ function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
|
2798
3476
|
}
|
|
2799
3477
|
}
|
|
2800
3478
|
function validateFieldNode(ctx, field) {
|
|
2801
|
-
validateConstraints(ctx, field.name, field.type,
|
|
3479
|
+
validateConstraints(ctx, field.name, field.type, [
|
|
3480
|
+
...collectReferencedTypeConstraints(ctx, field.type),
|
|
3481
|
+
...field.constraints
|
|
3482
|
+
]);
|
|
2802
3483
|
if (field.type.kind === "object") {
|
|
2803
3484
|
for (const prop of field.type.properties) {
|
|
2804
3485
|
validateObjectProperty(ctx, field.name, prop);
|
|
@@ -2807,7 +3488,10 @@ function validateFieldNode(ctx, field) {
|
|
|
2807
3488
|
}
|
|
2808
3489
|
function validateObjectProperty(ctx, parentName, prop) {
|
|
2809
3490
|
const qualifiedName = `${parentName}.${prop.name}`;
|
|
2810
|
-
validateConstraints(ctx, qualifiedName, prop.type,
|
|
3491
|
+
validateConstraints(ctx, qualifiedName, prop.type, [
|
|
3492
|
+
...collectReferencedTypeConstraints(ctx, prop.type),
|
|
3493
|
+
...prop.constraints
|
|
3494
|
+
]);
|
|
2811
3495
|
if (prop.type.kind === "object") {
|
|
2812
3496
|
for (const nestedProp of prop.type.properties) {
|
|
2813
3497
|
validateObjectProperty(ctx, qualifiedName, nestedProp);
|
|
@@ -2820,6 +3504,7 @@ function validateConstraints(ctx, name, type, constraints) {
|
|
|
2820
3504
|
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
2821
3505
|
checkConstContradictions(ctx, name, constraints);
|
|
2822
3506
|
checkConstraintBroadening(ctx, name, constraints);
|
|
3507
|
+
checkCustomConstraintSemantics(ctx, name, constraints);
|
|
2823
3508
|
checkTypeApplicability(ctx, name, type, constraints);
|
|
2824
3509
|
}
|
|
2825
3510
|
function validateElement(ctx, element) {
|
|
@@ -2858,10 +3543,43 @@ function validateIR(ir, options) {
|
|
|
2858
3543
|
};
|
|
2859
3544
|
}
|
|
2860
3545
|
|
|
3546
|
+
// src/generators/class-schema.ts
|
|
3547
|
+
function generateClassSchemas(analysis, source, options) {
|
|
3548
|
+
const ir = canonicalizeTSDoc(analysis, source);
|
|
3549
|
+
const validationResult = validateIR(ir, {
|
|
3550
|
+
...options?.extensionRegistry !== void 0 && {
|
|
3551
|
+
extensionRegistry: options.extensionRegistry
|
|
3552
|
+
},
|
|
3553
|
+
...options?.vendorPrefix !== void 0 && { vendorPrefix: options.vendorPrefix }
|
|
3554
|
+
});
|
|
3555
|
+
if (!validationResult.valid) {
|
|
3556
|
+
throw new Error(formatValidationError(validationResult.diagnostics));
|
|
3557
|
+
}
|
|
3558
|
+
return {
|
|
3559
|
+
jsonSchema: generateJsonSchemaFromIR(ir, options),
|
|
3560
|
+
uiSchema: generateUiSchemaFromIR(ir)
|
|
3561
|
+
};
|
|
3562
|
+
}
|
|
3563
|
+
function formatValidationError(diagnostics) {
|
|
3564
|
+
const lines = diagnostics.map((diagnostic) => {
|
|
3565
|
+
const primary = formatLocation(diagnostic.primaryLocation);
|
|
3566
|
+
const related = diagnostic.relatedLocations.length > 0 ? ` [related: ${diagnostic.relatedLocations.map(formatLocation).join(", ")}]` : "";
|
|
3567
|
+
return `${diagnostic.code}: ${diagnostic.message} (${primary})${related}`;
|
|
3568
|
+
});
|
|
3569
|
+
return `FormSpec validation failed:
|
|
3570
|
+
${lines.map((line) => `- ${line}`).join("\n")}`;
|
|
3571
|
+
}
|
|
3572
|
+
function formatLocation(location) {
|
|
3573
|
+
return `${location.file}:${String(location.line)}:${String(location.column)}`;
|
|
3574
|
+
}
|
|
3575
|
+
|
|
2861
3576
|
// src/extensions/registry.ts
|
|
2862
3577
|
function createExtensionRegistry(extensions) {
|
|
2863
3578
|
const typeMap = /* @__PURE__ */ new Map();
|
|
3579
|
+
const typeNameMap = /* @__PURE__ */ new Map();
|
|
2864
3580
|
const constraintMap = /* @__PURE__ */ new Map();
|
|
3581
|
+
const constraintTagMap = /* @__PURE__ */ new Map();
|
|
3582
|
+
const builtinBroadeningMap = /* @__PURE__ */ new Map();
|
|
2865
3583
|
const annotationMap = /* @__PURE__ */ new Map();
|
|
2866
3584
|
for (const ext of extensions) {
|
|
2867
3585
|
if (ext.types !== void 0) {
|
|
@@ -2871,6 +3589,27 @@ function createExtensionRegistry(extensions) {
|
|
|
2871
3589
|
throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
|
|
2872
3590
|
}
|
|
2873
3591
|
typeMap.set(qualifiedId, type);
|
|
3592
|
+
for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
|
|
3593
|
+
if (typeNameMap.has(sourceTypeName)) {
|
|
3594
|
+
throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
|
|
3595
|
+
}
|
|
3596
|
+
typeNameMap.set(sourceTypeName, {
|
|
3597
|
+
extensionId: ext.extensionId,
|
|
3598
|
+
registration: type
|
|
3599
|
+
});
|
|
3600
|
+
}
|
|
3601
|
+
if (type.builtinConstraintBroadenings !== void 0) {
|
|
3602
|
+
for (const broadening of type.builtinConstraintBroadenings) {
|
|
3603
|
+
const key = `${qualifiedId}:${broadening.tagName}`;
|
|
3604
|
+
if (builtinBroadeningMap.has(key)) {
|
|
3605
|
+
throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
|
|
3606
|
+
}
|
|
3607
|
+
builtinBroadeningMap.set(key, {
|
|
3608
|
+
extensionId: ext.extensionId,
|
|
3609
|
+
registration: broadening
|
|
3610
|
+
});
|
|
3611
|
+
}
|
|
3612
|
+
}
|
|
2874
3613
|
}
|
|
2875
3614
|
}
|
|
2876
3615
|
if (ext.constraints !== void 0) {
|
|
@@ -2882,6 +3621,17 @@ function createExtensionRegistry(extensions) {
|
|
|
2882
3621
|
constraintMap.set(qualifiedId, constraint);
|
|
2883
3622
|
}
|
|
2884
3623
|
}
|
|
3624
|
+
if (ext.constraintTags !== void 0) {
|
|
3625
|
+
for (const tag of ext.constraintTags) {
|
|
3626
|
+
if (constraintTagMap.has(tag.tagName)) {
|
|
3627
|
+
throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
|
|
3628
|
+
}
|
|
3629
|
+
constraintTagMap.set(tag.tagName, {
|
|
3630
|
+
extensionId: ext.extensionId,
|
|
3631
|
+
registration: tag
|
|
3632
|
+
});
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
2885
3635
|
if (ext.annotations !== void 0) {
|
|
2886
3636
|
for (const annotation of ext.annotations) {
|
|
2887
3637
|
const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
|
|
@@ -2895,13 +3645,16 @@ function createExtensionRegistry(extensions) {
|
|
|
2895
3645
|
return {
|
|
2896
3646
|
extensions,
|
|
2897
3647
|
findType: (typeId) => typeMap.get(typeId),
|
|
3648
|
+
findTypeByName: (typeName) => typeNameMap.get(typeName),
|
|
2898
3649
|
findConstraint: (constraintId) => constraintMap.get(constraintId),
|
|
3650
|
+
findConstraintTag: (tagName) => constraintTagMap.get(tagName),
|
|
3651
|
+
findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
|
|
2899
3652
|
findAnnotation: (annotationId) => annotationMap.get(annotationId)
|
|
2900
3653
|
};
|
|
2901
3654
|
}
|
|
2902
3655
|
|
|
2903
3656
|
// src/generators/method-schema.ts
|
|
2904
|
-
var
|
|
3657
|
+
var import_core5 = require("@formspec/core");
|
|
2905
3658
|
function typeToJsonSchema(type, checker) {
|
|
2906
3659
|
const typeRegistry = {};
|
|
2907
3660
|
const visiting = /* @__PURE__ */ new Set();
|
|
@@ -2909,7 +3662,7 @@ function typeToJsonSchema(type, checker) {
|
|
|
2909
3662
|
const fieldProvenance = { surface: "tsdoc", file: "", line: 0, column: 0 };
|
|
2910
3663
|
const ir = {
|
|
2911
3664
|
kind: "form-ir",
|
|
2912
|
-
irVersion:
|
|
3665
|
+
irVersion: import_core5.IR_VERSION,
|
|
2913
3666
|
elements: [
|
|
2914
3667
|
{
|
|
2915
3668
|
kind: "field",
|
|
@@ -2921,6 +3674,7 @@ function typeToJsonSchema(type, checker) {
|
|
|
2921
3674
|
provenance: fieldProvenance
|
|
2922
3675
|
}
|
|
2923
3676
|
],
|
|
3677
|
+
rootAnnotations: [],
|
|
2924
3678
|
typeRegistry,
|
|
2925
3679
|
provenance: fieldProvenance
|
|
2926
3680
|
};
|