@formspec/build 0.1.0-alpha.19 → 0.1.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/fixtures/class-schema-regressions.d.ts +4 -0
- package/dist/__tests__/fixtures/class-schema-regressions.d.ts.map +1 -1
- package/dist/__tests__/parity/utils.d.ts.map +1 -1
- package/dist/analyzer/class-analyzer.d.ts +4 -1
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/analyzer/jsdoc-constraints.d.ts +2 -1
- package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
- package/dist/analyzer/tsdoc-parser.d.ts +13 -3
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
- package/dist/browser.cjs +30 -748
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.js +32 -748
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +14 -14
- package/dist/cli.cjs +691 -1101
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +704 -1100
- package/dist/cli.js.map +1 -1
- package/dist/generators/class-schema.d.ts.map +1 -1
- package/dist/index.cjs +689 -1095
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +703 -1095
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +663 -1069
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +675 -1067
- package/dist/internals.js.map +1 -1
- package/dist/ui-schema/schema.d.ts +14 -14
- package/dist/validate/constraint-validator.d.ts +6 -45
- package/dist/validate/constraint-validator.d.ts.map +1 -1
- package/package.json +2 -1
- package/dist/__tests__/json-utils.test.d.ts +0 -5
- package/dist/__tests__/json-utils.test.d.ts.map +0 -1
- package/dist/analyzer/json-utils.d.ts +0 -22
- package/dist/analyzer/json-utils.d.ts.map +0 -1
package/dist/internals.cjs
CHANGED
|
@@ -449,32 +449,9 @@ var ts2 = __toESM(require("typescript"), 1);
|
|
|
449
449
|
|
|
450
450
|
// src/analyzer/tsdoc-parser.ts
|
|
451
451
|
var ts = __toESM(require("typescript"), 1);
|
|
452
|
+
var import_analysis = require("@formspec/analysis");
|
|
452
453
|
var import_tsdoc = require("@microsoft/tsdoc");
|
|
453
454
|
var import_core3 = require("@formspec/core");
|
|
454
|
-
|
|
455
|
-
// src/analyzer/json-utils.ts
|
|
456
|
-
function tryParseJson(text) {
|
|
457
|
-
try {
|
|
458
|
-
return JSON.parse(text);
|
|
459
|
-
} catch {
|
|
460
|
-
return null;
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// src/analyzer/tsdoc-parser.ts
|
|
465
|
-
var NUMERIC_CONSTRAINT_MAP = {
|
|
466
|
-
minimum: "minimum",
|
|
467
|
-
maximum: "maximum",
|
|
468
|
-
exclusiveMinimum: "exclusiveMinimum",
|
|
469
|
-
exclusiveMaximum: "exclusiveMaximum",
|
|
470
|
-
multipleOf: "multipleOf"
|
|
471
|
-
};
|
|
472
|
-
var LENGTH_CONSTRAINT_MAP = {
|
|
473
|
-
minLength: "minLength",
|
|
474
|
-
maxLength: "maxLength",
|
|
475
|
-
minItems: "minItems",
|
|
476
|
-
maxItems: "maxItems"
|
|
477
|
-
};
|
|
478
455
|
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
|
|
479
456
|
function createFormSpecTSDocConfig(extensionTagNames = []) {
|
|
480
457
|
const config = new import_tsdoc.TSDocConfiguration();
|
|
@@ -507,7 +484,294 @@ function createFormSpecTSDocConfig(extensionTagNames = []) {
|
|
|
507
484
|
}
|
|
508
485
|
return config;
|
|
509
486
|
}
|
|
487
|
+
function sharedCommentSyntaxOptions(options, offset) {
|
|
488
|
+
const extensions = options?.extensionRegistry?.extensions;
|
|
489
|
+
return {
|
|
490
|
+
...offset !== void 0 ? { offset } : {},
|
|
491
|
+
...extensions !== void 0 ? { extensions } : {}
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
function sharedTagValueOptions(options) {
|
|
495
|
+
return {
|
|
496
|
+
...options?.extensionRegistry !== void 0 ? { registry: options.extensionRegistry } : {},
|
|
497
|
+
...options?.fieldType !== void 0 ? { fieldType: options.fieldType } : {}
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
var SYNTHETIC_TYPE_FORMAT_FLAGS = ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope;
|
|
501
|
+
function buildSupportingDeclarations(sourceFile) {
|
|
502
|
+
return sourceFile.statements.filter(
|
|
503
|
+
(statement) => !ts.isImportDeclaration(statement) && !ts.isImportEqualsDeclaration(statement) && !(ts.isExportDeclaration(statement) && statement.moduleSpecifier !== void 0)
|
|
504
|
+
).map((statement) => statement.getText(sourceFile));
|
|
505
|
+
}
|
|
506
|
+
function renderSyntheticArgumentExpression(valueKind, argumentText) {
|
|
507
|
+
const trimmed = argumentText.trim();
|
|
508
|
+
if (trimmed === "") {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
switch (valueKind) {
|
|
512
|
+
case "number":
|
|
513
|
+
case "integer":
|
|
514
|
+
case "signedInteger":
|
|
515
|
+
return Number.isFinite(Number(trimmed)) ? trimmed : JSON.stringify(trimmed);
|
|
516
|
+
case "string":
|
|
517
|
+
return JSON.stringify(argumentText);
|
|
518
|
+
case "json":
|
|
519
|
+
try {
|
|
520
|
+
JSON.parse(trimmed);
|
|
521
|
+
return `(${trimmed})`;
|
|
522
|
+
} catch {
|
|
523
|
+
return JSON.stringify(trimmed);
|
|
524
|
+
}
|
|
525
|
+
case "boolean":
|
|
526
|
+
return trimmed === "true" || trimmed === "false" ? trimmed : JSON.stringify(trimmed);
|
|
527
|
+
case "condition":
|
|
528
|
+
return "undefined as unknown as FormSpecCondition";
|
|
529
|
+
case null:
|
|
530
|
+
return null;
|
|
531
|
+
default: {
|
|
532
|
+
return String(valueKind);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
function getArrayElementType(type, checker) {
|
|
537
|
+
if (!checker.isArrayType(type)) {
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
return checker.getTypeArguments(type)[0] ?? null;
|
|
541
|
+
}
|
|
542
|
+
function supportsConstraintCapability(type, checker, capability) {
|
|
543
|
+
if (capability === void 0) {
|
|
544
|
+
return true;
|
|
545
|
+
}
|
|
546
|
+
if ((0, import_analysis.hasTypeSemanticCapability)(type, checker, capability)) {
|
|
547
|
+
return true;
|
|
548
|
+
}
|
|
549
|
+
if (capability === "string-like") {
|
|
550
|
+
const itemType = getArrayElementType(type, checker);
|
|
551
|
+
return itemType !== null && (0, import_analysis.hasTypeSemanticCapability)(itemType, checker, capability);
|
|
552
|
+
}
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
function makeDiagnostic(code, message, provenance) {
|
|
556
|
+
return {
|
|
557
|
+
code,
|
|
558
|
+
message,
|
|
559
|
+
severity: "error",
|
|
560
|
+
primaryLocation: provenance,
|
|
561
|
+
relatedLocations: []
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
function placementLabel(placement) {
|
|
565
|
+
switch (placement) {
|
|
566
|
+
case "class":
|
|
567
|
+
return "class declarations";
|
|
568
|
+
case "class-field":
|
|
569
|
+
return "class fields";
|
|
570
|
+
case "class-method":
|
|
571
|
+
return "class methods";
|
|
572
|
+
case "interface":
|
|
573
|
+
return "interface declarations";
|
|
574
|
+
case "interface-field":
|
|
575
|
+
return "interface fields";
|
|
576
|
+
case "type-alias":
|
|
577
|
+
return "type aliases";
|
|
578
|
+
case "type-alias-field":
|
|
579
|
+
return "type-alias properties";
|
|
580
|
+
case "variable":
|
|
581
|
+
return "variables";
|
|
582
|
+
case "function":
|
|
583
|
+
return "functions";
|
|
584
|
+
case "function-parameter":
|
|
585
|
+
return "function parameters";
|
|
586
|
+
case "method-parameter":
|
|
587
|
+
return "method parameters";
|
|
588
|
+
default: {
|
|
589
|
+
const exhaustive = placement;
|
|
590
|
+
return String(exhaustive);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
function capabilityLabel(capability) {
|
|
595
|
+
switch (capability) {
|
|
596
|
+
case "numeric-comparable":
|
|
597
|
+
return "number";
|
|
598
|
+
case "string-like":
|
|
599
|
+
return "string";
|
|
600
|
+
case "array-like":
|
|
601
|
+
return "array";
|
|
602
|
+
case "enum-member-addressable":
|
|
603
|
+
return "enum";
|
|
604
|
+
case "json-like":
|
|
605
|
+
return "JSON-compatible";
|
|
606
|
+
case "object-like":
|
|
607
|
+
return "object";
|
|
608
|
+
case "condition-like":
|
|
609
|
+
return "conditional";
|
|
610
|
+
case void 0:
|
|
611
|
+
return "compatible";
|
|
612
|
+
default:
|
|
613
|
+
return capability;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
function getBroadenedCustomTypeId(fieldType) {
|
|
617
|
+
if (fieldType?.kind === "custom") {
|
|
618
|
+
return fieldType.typeId;
|
|
619
|
+
}
|
|
620
|
+
if (fieldType?.kind !== "union") {
|
|
621
|
+
return void 0;
|
|
622
|
+
}
|
|
623
|
+
const customMembers = fieldType.members.filter(
|
|
624
|
+
(member) => member.kind === "custom"
|
|
625
|
+
);
|
|
626
|
+
if (customMembers.length !== 1) {
|
|
627
|
+
return void 0;
|
|
628
|
+
}
|
|
629
|
+
const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
|
|
630
|
+
const allOtherMembersAreNull = nonCustomMembers.every(
|
|
631
|
+
(member) => member.kind === "primitive" && member.primitiveKind === "null"
|
|
632
|
+
);
|
|
633
|
+
const customMember = customMembers[0];
|
|
634
|
+
return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
|
|
635
|
+
}
|
|
636
|
+
function hasBuiltinConstraintBroadening(tagName, options) {
|
|
637
|
+
const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
|
|
638
|
+
return broadenedTypeId !== void 0 && options?.extensionRegistry?.findBuiltinConstraintBroadening(broadenedTypeId, tagName) !== void 0;
|
|
639
|
+
}
|
|
640
|
+
function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, parsedTag, provenance, supportingDeclarations, options) {
|
|
641
|
+
if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
|
|
642
|
+
return [];
|
|
643
|
+
}
|
|
644
|
+
const checker = options?.checker;
|
|
645
|
+
const subjectType = options?.subjectType;
|
|
646
|
+
if (checker === void 0 || subjectType === void 0) {
|
|
647
|
+
return [];
|
|
648
|
+
}
|
|
649
|
+
const placement = (0, import_analysis.resolveDeclarationPlacement)(node);
|
|
650
|
+
if (placement === null) {
|
|
651
|
+
return [];
|
|
652
|
+
}
|
|
653
|
+
const definition = (0, import_analysis.getTagDefinition)(tagName, options?.extensionRegistry?.extensions);
|
|
654
|
+
if (definition === null) {
|
|
655
|
+
return [];
|
|
656
|
+
}
|
|
657
|
+
if (!definition.placements.includes(placement)) {
|
|
658
|
+
return [
|
|
659
|
+
makeDiagnostic(
|
|
660
|
+
"INVALID_TAG_PLACEMENT",
|
|
661
|
+
`Tag "@${tagName}" is not allowed on ${placementLabel(placement)}.`,
|
|
662
|
+
provenance
|
|
663
|
+
)
|
|
664
|
+
];
|
|
665
|
+
}
|
|
666
|
+
const target = parsedTag?.target ?? null;
|
|
667
|
+
const hasBroadening = target === null && hasBuiltinConstraintBroadening(tagName, options);
|
|
668
|
+
if (target !== null) {
|
|
669
|
+
if (target.kind !== "path") {
|
|
670
|
+
return [
|
|
671
|
+
makeDiagnostic(
|
|
672
|
+
"UNSUPPORTED_TARGETING_SYNTAX",
|
|
673
|
+
`Tag "@${tagName}" does not support ${target.kind} targeting syntax.`,
|
|
674
|
+
provenance
|
|
675
|
+
)
|
|
676
|
+
];
|
|
677
|
+
}
|
|
678
|
+
if (!target.valid || target.path === null) {
|
|
679
|
+
return [
|
|
680
|
+
makeDiagnostic(
|
|
681
|
+
"UNSUPPORTED_TARGETING_SYNTAX",
|
|
682
|
+
`Tag "@${tagName}" has invalid path targeting syntax.`,
|
|
683
|
+
provenance
|
|
684
|
+
)
|
|
685
|
+
];
|
|
686
|
+
}
|
|
687
|
+
const resolution = (0, import_analysis.resolvePathTargetType)(subjectType, checker, target.path.segments);
|
|
688
|
+
if (resolution.kind === "missing-property") {
|
|
689
|
+
return [
|
|
690
|
+
makeDiagnostic(
|
|
691
|
+
"UNKNOWN_PATH_TARGET",
|
|
692
|
+
`Target "${target.rawText}": path-targeted constraint "${tagName}" references unknown path segment "${resolution.segment}"`,
|
|
693
|
+
provenance
|
|
694
|
+
)
|
|
695
|
+
];
|
|
696
|
+
}
|
|
697
|
+
if (resolution.kind === "unresolvable") {
|
|
698
|
+
const actualType = checker.typeToString(resolution.type, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
699
|
+
return [
|
|
700
|
+
makeDiagnostic(
|
|
701
|
+
"TYPE_MISMATCH",
|
|
702
|
+
`Target "${target.rawText}": path-targeted constraint "${tagName}" is invalid because type "${actualType}" cannot be traversed`,
|
|
703
|
+
provenance
|
|
704
|
+
)
|
|
705
|
+
];
|
|
706
|
+
}
|
|
707
|
+
const requiredCapability = definition.capabilities[0];
|
|
708
|
+
if (requiredCapability !== void 0 && !supportsConstraintCapability(resolution.type, checker, requiredCapability)) {
|
|
709
|
+
const actualType = checker.typeToString(resolution.type, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
710
|
+
return [
|
|
711
|
+
makeDiagnostic(
|
|
712
|
+
"TYPE_MISMATCH",
|
|
713
|
+
`Target "${target.rawText}": constraint "${tagName}" is only valid on ${capabilityLabel(requiredCapability)} targets, but field type is "${actualType}"`,
|
|
714
|
+
provenance
|
|
715
|
+
)
|
|
716
|
+
];
|
|
717
|
+
}
|
|
718
|
+
} else if (!hasBroadening) {
|
|
719
|
+
const requiredCapability = definition.capabilities[0];
|
|
720
|
+
if (requiredCapability !== void 0 && !supportsConstraintCapability(subjectType, checker, requiredCapability)) {
|
|
721
|
+
const actualType = checker.typeToString(subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
722
|
+
return [
|
|
723
|
+
makeDiagnostic(
|
|
724
|
+
"TYPE_MISMATCH",
|
|
725
|
+
`Target "${node.getText(sourceFile)}": constraint "${tagName}" is only valid on ${capabilityLabel(requiredCapability)} targets, but field type is "${actualType}"`,
|
|
726
|
+
provenance
|
|
727
|
+
)
|
|
728
|
+
];
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
const argumentExpression = renderSyntheticArgumentExpression(
|
|
732
|
+
definition.valueKind,
|
|
733
|
+
parsedTag?.argumentText ?? ""
|
|
734
|
+
);
|
|
735
|
+
if (definition.requiresArgument && argumentExpression === null) {
|
|
736
|
+
return [];
|
|
737
|
+
}
|
|
738
|
+
if (hasBroadening) {
|
|
739
|
+
return [];
|
|
740
|
+
}
|
|
741
|
+
const subjectTypeText = checker.typeToString(subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
742
|
+
const hostType = options?.hostType ?? subjectType;
|
|
743
|
+
const hostTypeText = checker.typeToString(hostType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
744
|
+
const result = (0, import_analysis.checkSyntheticTagApplication)({
|
|
745
|
+
tagName,
|
|
746
|
+
placement,
|
|
747
|
+
hostType: hostTypeText,
|
|
748
|
+
subjectType: subjectTypeText,
|
|
749
|
+
...target?.kind === "path" ? { target: { kind: "path", text: target.rawText } } : {},
|
|
750
|
+
...argumentExpression !== null ? { argumentExpression } : {},
|
|
751
|
+
supportingDeclarations,
|
|
752
|
+
...options?.extensionRegistry !== void 0 ? {
|
|
753
|
+
extensions: options.extensionRegistry.extensions.map((extension) => ({
|
|
754
|
+
extensionId: extension.extensionId,
|
|
755
|
+
...extension.constraintTags !== void 0 ? {
|
|
756
|
+
constraintTags: extension.constraintTags.map((tag) => ({ tagName: tag.tagName }))
|
|
757
|
+
} : {}
|
|
758
|
+
}))
|
|
759
|
+
} : {}
|
|
760
|
+
});
|
|
761
|
+
if (result.diagnostics.length === 0) {
|
|
762
|
+
return [];
|
|
763
|
+
}
|
|
764
|
+
const expectedLabel = definition.valueKind === null ? "compatible argument" : capabilityLabel(definition.valueKind);
|
|
765
|
+
return [
|
|
766
|
+
makeDiagnostic(
|
|
767
|
+
"TYPE_MISMATCH",
|
|
768
|
+
`Tag "@${tagName}" received an invalid argument for ${expectedLabel}.`,
|
|
769
|
+
provenance
|
|
770
|
+
)
|
|
771
|
+
];
|
|
772
|
+
}
|
|
510
773
|
var parserCache = /* @__PURE__ */ new Map();
|
|
774
|
+
var parseResultCache = /* @__PURE__ */ new Map();
|
|
511
775
|
function getParser(options) {
|
|
512
776
|
const extensionTagNames = [
|
|
513
777
|
...options?.extensionRegistry?.extensions.flatMap(
|
|
@@ -523,18 +787,54 @@ function getParser(options) {
|
|
|
523
787
|
parserCache.set(cacheKey, parser);
|
|
524
788
|
return parser;
|
|
525
789
|
}
|
|
790
|
+
function getExtensionRegistryCacheKey(registry) {
|
|
791
|
+
if (registry === void 0) {
|
|
792
|
+
return "";
|
|
793
|
+
}
|
|
794
|
+
return registry.extensions.map(
|
|
795
|
+
(extension) => JSON.stringify({
|
|
796
|
+
extensionId: extension.extensionId,
|
|
797
|
+
typeNames: extension.types?.map((type) => type.typeName) ?? [],
|
|
798
|
+
constraintTags: extension.constraintTags?.map((tag) => tag.tagName) ?? []
|
|
799
|
+
})
|
|
800
|
+
).join("|");
|
|
801
|
+
}
|
|
802
|
+
function getParseCacheKey(node, file, options) {
|
|
803
|
+
const sourceFile = node.getSourceFile();
|
|
804
|
+
const checker = options?.checker;
|
|
805
|
+
return JSON.stringify({
|
|
806
|
+
file,
|
|
807
|
+
sourceFile: sourceFile.fileName,
|
|
808
|
+
sourceText: sourceFile.text,
|
|
809
|
+
start: node.getFullStart(),
|
|
810
|
+
end: node.getEnd(),
|
|
811
|
+
fieldType: options?.fieldType ?? null,
|
|
812
|
+
subjectType: checker !== void 0 && options?.subjectType !== void 0 ? checker.typeToString(options.subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS) : null,
|
|
813
|
+
hostType: checker !== void 0 && options?.hostType !== void 0 ? checker.typeToString(options.hostType, node, SYNTHETIC_TYPE_FORMAT_FLAGS) : null,
|
|
814
|
+
extensions: getExtensionRegistryCacheKey(options?.extensionRegistry)
|
|
815
|
+
});
|
|
816
|
+
}
|
|
526
817
|
function parseTSDocTags(node, file = "", options) {
|
|
818
|
+
const cacheKey = getParseCacheKey(node, file, options);
|
|
819
|
+
const cached = parseResultCache.get(cacheKey);
|
|
820
|
+
if (cached !== void 0) {
|
|
821
|
+
return cached;
|
|
822
|
+
}
|
|
527
823
|
const constraints = [];
|
|
528
824
|
const annotations = [];
|
|
825
|
+
const diagnostics = [];
|
|
529
826
|
let displayName;
|
|
530
827
|
let description;
|
|
531
828
|
let placeholder;
|
|
532
829
|
let displayNameProvenance;
|
|
533
830
|
let descriptionProvenance;
|
|
534
831
|
let placeholderProvenance;
|
|
832
|
+
const rawTextTags = [];
|
|
535
833
|
const sourceFile = node.getSourceFile();
|
|
536
834
|
const sourceText = sourceFile.getFullText();
|
|
835
|
+
const supportingDeclarations = buildSupportingDeclarations(sourceFile);
|
|
537
836
|
const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
837
|
+
const rawTextFallbacks = collectRawTextFallbacks(node, file);
|
|
538
838
|
if (commentRanges) {
|
|
539
839
|
for (const range of commentRanges) {
|
|
540
840
|
if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
|
|
@@ -549,12 +849,33 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
549
849
|
import_tsdoc.TextRange.fromStringRange(sourceText, range.pos, range.end)
|
|
550
850
|
);
|
|
551
851
|
const docComment = parserContext.docComment;
|
|
852
|
+
const parsedComment = (0, import_analysis.parseCommentBlock)(
|
|
853
|
+
commentText,
|
|
854
|
+
sharedCommentSyntaxOptions(options, range.pos)
|
|
855
|
+
);
|
|
856
|
+
let parsedTagCursor = 0;
|
|
857
|
+
const nextParsedTag = (normalizedTagName) => {
|
|
858
|
+
while (parsedTagCursor < parsedComment.tags.length) {
|
|
859
|
+
const candidate = parsedComment.tags[parsedTagCursor];
|
|
860
|
+
parsedTagCursor += 1;
|
|
861
|
+
if (candidate?.normalizedTagName === normalizedTagName) {
|
|
862
|
+
return candidate;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
return null;
|
|
866
|
+
};
|
|
867
|
+
for (const parsedTag of parsedComment.tags) {
|
|
868
|
+
if (TAGS_REQUIRING_RAW_TEXT.has(parsedTag.normalizedTagName)) {
|
|
869
|
+
rawTextTags.push({ tag: parsedTag, commentText, commentOffset: range.pos });
|
|
870
|
+
}
|
|
871
|
+
}
|
|
552
872
|
for (const block of docComment.customBlocks) {
|
|
553
873
|
const tagName = (0, import_core3.normalizeConstraintTagName)(block.blockTag.tagName.substring(1));
|
|
874
|
+
const parsedTag = nextParsedTag(tagName);
|
|
554
875
|
if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
|
|
555
|
-
const text2 =
|
|
876
|
+
const text2 = getBestBlockPayloadText(parsedTag, commentText, range.pos, block);
|
|
556
877
|
if (text2 === "") continue;
|
|
557
|
-
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
878
|
+
const provenance2 = parsedTag !== null ? provenanceForParsedTag(parsedTag, sourceFile, file) : provenanceForComment(range, sourceFile, file, tagName);
|
|
558
879
|
switch (tagName) {
|
|
559
880
|
case "displayName":
|
|
560
881
|
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
@@ -584,11 +905,29 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
584
905
|
continue;
|
|
585
906
|
}
|
|
586
907
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
587
|
-
const text =
|
|
908
|
+
const text = getBestBlockPayloadText(parsedTag, commentText, range.pos, block);
|
|
588
909
|
const expectedType = (0, import_core3.isBuiltinConstraintName)(tagName) ? import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
|
|
589
910
|
if (text === "" && expectedType !== "boolean") continue;
|
|
590
|
-
const provenance = provenanceForComment(range, sourceFile, file, tagName);
|
|
591
|
-
const
|
|
911
|
+
const provenance = parsedTag !== null ? provenanceForParsedTag(parsedTag, sourceFile, file) : provenanceForComment(range, sourceFile, file, tagName);
|
|
912
|
+
const compilerDiagnostics = buildCompilerBackedConstraintDiagnostics(
|
|
913
|
+
node,
|
|
914
|
+
sourceFile,
|
|
915
|
+
tagName,
|
|
916
|
+
parsedTag,
|
|
917
|
+
provenance,
|
|
918
|
+
supportingDeclarations,
|
|
919
|
+
options
|
|
920
|
+
);
|
|
921
|
+
if (compilerDiagnostics.length > 0) {
|
|
922
|
+
diagnostics.push(...compilerDiagnostics);
|
|
923
|
+
continue;
|
|
924
|
+
}
|
|
925
|
+
const constraintNode = (0, import_analysis.parseConstraintTagValue)(
|
|
926
|
+
tagName,
|
|
927
|
+
text,
|
|
928
|
+
provenance,
|
|
929
|
+
sharedTagValueOptions(options)
|
|
930
|
+
);
|
|
592
931
|
if (constraintNode) {
|
|
593
932
|
constraints.push(constraintNode);
|
|
594
933
|
}
|
|
@@ -642,57 +981,114 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
642
981
|
provenance: placeholderProvenance
|
|
643
982
|
});
|
|
644
983
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
984
|
+
if (rawTextTags.length > 0) {
|
|
985
|
+
for (const rawTextTag of rawTextTags) {
|
|
986
|
+
const fallbackQueue = rawTextFallbacks.get(rawTextTag.tag.normalizedTagName);
|
|
987
|
+
const fallback = fallbackQueue?.shift();
|
|
988
|
+
const text = choosePreferredPayloadText(
|
|
989
|
+
getSharedPayloadText(rawTextTag.tag, rawTextTag.commentText, rawTextTag.commentOffset),
|
|
990
|
+
fallback?.text ?? ""
|
|
991
|
+
);
|
|
992
|
+
if (text === "") continue;
|
|
993
|
+
const provenance = provenanceForParsedTag(rawTextTag.tag, sourceFile, file);
|
|
994
|
+
if (rawTextTag.tag.normalizedTagName === "defaultValue") {
|
|
995
|
+
const defaultValueNode = (0, import_analysis.parseDefaultValueTagValue)(text, provenance);
|
|
996
|
+
annotations.push(defaultValueNode);
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
const compilerDiagnostics = buildCompilerBackedConstraintDiagnostics(
|
|
1000
|
+
node,
|
|
1001
|
+
sourceFile,
|
|
1002
|
+
rawTextTag.tag.normalizedTagName,
|
|
1003
|
+
rawTextTag.tag,
|
|
1004
|
+
provenance,
|
|
1005
|
+
supportingDeclarations,
|
|
1006
|
+
options
|
|
1007
|
+
);
|
|
1008
|
+
if (compilerDiagnostics.length > 0) {
|
|
1009
|
+
diagnostics.push(...compilerDiagnostics);
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
1012
|
+
const constraintNode = (0, import_analysis.parseConstraintTagValue)(
|
|
1013
|
+
rawTextTag.tag.normalizedTagName,
|
|
1014
|
+
text,
|
|
1015
|
+
provenance,
|
|
1016
|
+
sharedTagValueOptions(options)
|
|
1017
|
+
);
|
|
1018
|
+
if (constraintNode) {
|
|
1019
|
+
constraints.push(constraintNode);
|
|
1020
|
+
}
|
|
657
1021
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
1022
|
+
}
|
|
1023
|
+
for (const [tagName, fallbacks] of rawTextFallbacks) {
|
|
1024
|
+
for (const fallback of fallbacks) {
|
|
1025
|
+
const text = fallback.text.trim();
|
|
1026
|
+
if (text === "") continue;
|
|
1027
|
+
const provenance = fallback.provenance;
|
|
1028
|
+
if (tagName === "defaultValue") {
|
|
1029
|
+
const defaultValueNode = (0, import_analysis.parseDefaultValueTagValue)(text, provenance);
|
|
1030
|
+
annotations.push(defaultValueNode);
|
|
1031
|
+
continue;
|
|
1032
|
+
}
|
|
1033
|
+
const compilerDiagnostics = buildCompilerBackedConstraintDiagnostics(
|
|
1034
|
+
node,
|
|
1035
|
+
sourceFile,
|
|
1036
|
+
tagName,
|
|
1037
|
+
null,
|
|
1038
|
+
provenance,
|
|
1039
|
+
supportingDeclarations,
|
|
1040
|
+
options
|
|
1041
|
+
);
|
|
1042
|
+
if (compilerDiagnostics.length > 0) {
|
|
1043
|
+
diagnostics.push(...compilerDiagnostics);
|
|
1044
|
+
continue;
|
|
1045
|
+
}
|
|
1046
|
+
const constraintNode = (0, import_analysis.parseConstraintTagValue)(
|
|
1047
|
+
tagName,
|
|
1048
|
+
text,
|
|
1049
|
+
provenance,
|
|
1050
|
+
sharedTagValueOptions(options)
|
|
1051
|
+
);
|
|
1052
|
+
if (constraintNode) {
|
|
1053
|
+
constraints.push(constraintNode);
|
|
1054
|
+
}
|
|
661
1055
|
}
|
|
662
1056
|
}
|
|
663
|
-
|
|
1057
|
+
const result = { constraints, annotations, diagnostics };
|
|
1058
|
+
parseResultCache.set(cacheKey, result);
|
|
1059
|
+
return result;
|
|
664
1060
|
}
|
|
665
1061
|
function extractDisplayNameMetadata(node) {
|
|
666
1062
|
let displayName;
|
|
667
1063
|
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
1064
|
+
const sourceFile = node.getSourceFile();
|
|
1065
|
+
const sourceText = sourceFile.getFullText();
|
|
1066
|
+
const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
1067
|
+
if (commentRanges) {
|
|
1068
|
+
for (const range of commentRanges) {
|
|
1069
|
+
if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) continue;
|
|
1070
|
+
const commentText = sourceText.substring(range.pos, range.end);
|
|
1071
|
+
if (!commentText.startsWith("/**")) continue;
|
|
1072
|
+
const parsed = (0, import_analysis.parseCommentBlock)(commentText);
|
|
1073
|
+
for (const tag of parsed.tags) {
|
|
1074
|
+
if (tag.normalizedTagName !== "displayName") {
|
|
1075
|
+
continue;
|
|
1076
|
+
}
|
|
1077
|
+
if (tag.target !== null && tag.argumentText !== "") {
|
|
1078
|
+
memberDisplayNames.set(tag.target.rawText, tag.argumentText);
|
|
1079
|
+
continue;
|
|
1080
|
+
}
|
|
1081
|
+
if (tag.argumentText !== "") {
|
|
1082
|
+
displayName ??= tag.argumentText;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
681
1086
|
}
|
|
682
1087
|
return {
|
|
683
1088
|
...displayName !== void 0 && { displayName },
|
|
684
1089
|
memberDisplayNames
|
|
685
1090
|
};
|
|
686
1091
|
}
|
|
687
|
-
function extractPathTarget(text) {
|
|
688
|
-
const trimmed = text.trimStart();
|
|
689
|
-
const match = /^:([a-zA-Z_]\w*)(?:\s+([\s\S]*))?$/.exec(trimmed);
|
|
690
|
-
if (!match?.[1]) return null;
|
|
691
|
-
return {
|
|
692
|
-
path: { segments: [match[1]] },
|
|
693
|
-
remainingText: match[2] ?? ""
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
1092
|
function extractBlockText(block) {
|
|
697
1093
|
return extractPlainText(block.content);
|
|
698
1094
|
}
|
|
@@ -711,219 +1107,48 @@ function extractPlainText(node) {
|
|
|
711
1107
|
}
|
|
712
1108
|
return result;
|
|
713
1109
|
}
|
|
714
|
-
function
|
|
715
|
-
const
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
if (
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
const pathResult = extractPathTarget(text);
|
|
723
|
-
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
724
|
-
const path2 = pathResult?.path;
|
|
725
|
-
const expectedType = import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
|
|
726
|
-
if (expectedType === "number") {
|
|
727
|
-
const value = Number(effectiveText);
|
|
728
|
-
if (Number.isNaN(value)) {
|
|
729
|
-
return null;
|
|
730
|
-
}
|
|
731
|
-
const numericKind = NUMERIC_CONSTRAINT_MAP[tagName];
|
|
732
|
-
if (numericKind) {
|
|
733
|
-
return {
|
|
734
|
-
kind: "constraint",
|
|
735
|
-
constraintKind: numericKind,
|
|
736
|
-
value,
|
|
737
|
-
...path2 && { path: path2 },
|
|
738
|
-
provenance
|
|
739
|
-
};
|
|
740
|
-
}
|
|
741
|
-
const lengthKind = LENGTH_CONSTRAINT_MAP[tagName];
|
|
742
|
-
if (lengthKind) {
|
|
743
|
-
return {
|
|
744
|
-
kind: "constraint",
|
|
745
|
-
constraintKind: lengthKind,
|
|
746
|
-
value,
|
|
747
|
-
...path2 && { path: path2 },
|
|
748
|
-
provenance
|
|
749
|
-
};
|
|
750
|
-
}
|
|
751
|
-
return null;
|
|
752
|
-
}
|
|
753
|
-
if (expectedType === "boolean") {
|
|
754
|
-
const trimmed = effectiveText.trim();
|
|
755
|
-
if (trimmed !== "" && trimmed !== "true") {
|
|
756
|
-
return null;
|
|
757
|
-
}
|
|
758
|
-
if (tagName === "uniqueItems") {
|
|
759
|
-
return {
|
|
760
|
-
kind: "constraint",
|
|
761
|
-
constraintKind: "uniqueItems",
|
|
762
|
-
value: true,
|
|
763
|
-
...path2 && { path: path2 },
|
|
764
|
-
provenance
|
|
765
|
-
};
|
|
766
|
-
}
|
|
767
|
-
return null;
|
|
1110
|
+
function choosePreferredPayloadText(primary, fallback) {
|
|
1111
|
+
const preferred = primary.trim();
|
|
1112
|
+
const alternate = fallback.trim();
|
|
1113
|
+
if (preferred === "") return alternate;
|
|
1114
|
+
if (alternate === "") return preferred;
|
|
1115
|
+
if (alternate.includes("\n")) return alternate;
|
|
1116
|
+
if (alternate.length > preferred.length && alternate.startsWith(preferred)) {
|
|
1117
|
+
return alternate;
|
|
768
1118
|
}
|
|
769
|
-
|
|
770
|
-
if (tagName === "const") {
|
|
771
|
-
const trimmedText = effectiveText.trim();
|
|
772
|
-
if (trimmedText === "") return null;
|
|
773
|
-
try {
|
|
774
|
-
const parsed2 = JSON.parse(trimmedText);
|
|
775
|
-
return {
|
|
776
|
-
kind: "constraint",
|
|
777
|
-
constraintKind: "const",
|
|
778
|
-
value: parsed2,
|
|
779
|
-
...path2 && { path: path2 },
|
|
780
|
-
provenance
|
|
781
|
-
};
|
|
782
|
-
} catch {
|
|
783
|
-
return {
|
|
784
|
-
kind: "constraint",
|
|
785
|
-
constraintKind: "const",
|
|
786
|
-
value: trimmedText,
|
|
787
|
-
...path2 && { path: path2 },
|
|
788
|
-
provenance
|
|
789
|
-
};
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
const parsed = tryParseJson(effectiveText);
|
|
793
|
-
if (!Array.isArray(parsed)) {
|
|
794
|
-
return null;
|
|
795
|
-
}
|
|
796
|
-
const members = [];
|
|
797
|
-
for (const item of parsed) {
|
|
798
|
-
if (typeof item === "string" || typeof item === "number") {
|
|
799
|
-
members.push(item);
|
|
800
|
-
} else if (typeof item === "object" && item !== null && "id" in item) {
|
|
801
|
-
const id = item["id"];
|
|
802
|
-
if (typeof id === "string" || typeof id === "number") {
|
|
803
|
-
members.push(id);
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
return {
|
|
808
|
-
kind: "constraint",
|
|
809
|
-
constraintKind: "allowedMembers",
|
|
810
|
-
members,
|
|
811
|
-
...path2 && { path: path2 },
|
|
812
|
-
provenance
|
|
813
|
-
};
|
|
814
|
-
}
|
|
815
|
-
return {
|
|
816
|
-
kind: "constraint",
|
|
817
|
-
constraintKind: "pattern",
|
|
818
|
-
pattern: effectiveText,
|
|
819
|
-
...path2 && { path: path2 },
|
|
820
|
-
provenance
|
|
821
|
-
};
|
|
1119
|
+
return preferred;
|
|
822
1120
|
}
|
|
823
|
-
function
|
|
824
|
-
|
|
825
|
-
|
|
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;
|
|
1121
|
+
function getSharedPayloadText(tag, commentText, commentOffset) {
|
|
1122
|
+
if (tag.payloadSpan === null) {
|
|
1123
|
+
return "";
|
|
844
1124
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
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
|
-
);
|
|
1125
|
+
return (0, import_analysis.sliceCommentSpan)(commentText, tag.payloadSpan, {
|
|
1126
|
+
offset: commentOffset
|
|
1127
|
+
}).trim();
|
|
861
1128
|
}
|
|
862
|
-
function
|
|
863
|
-
|
|
864
|
-
|
|
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;
|
|
1129
|
+
function getBestBlockPayloadText(tag, commentText, commentOffset, block) {
|
|
1130
|
+
const sharedText = tag === null ? "" : getSharedPayloadText(tag, commentText, commentOffset);
|
|
1131
|
+
const blockText = extractBlockText(block).replace(/\s+/g, " ").trim();
|
|
1132
|
+
return choosePreferredPayloadText(sharedText, blockText);
|
|
881
1133
|
}
|
|
882
|
-
function
|
|
883
|
-
const
|
|
884
|
-
const
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
);
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
compositionRule: registration.compositionRule,
|
|
896
|
-
...path2 && { path: path2 },
|
|
897
|
-
provenance
|
|
898
|
-
};
|
|
899
|
-
}
|
|
900
|
-
function parseDefaultValueValue(text, provenance) {
|
|
901
|
-
const trimmed = text.trim();
|
|
902
|
-
let value;
|
|
903
|
-
if (trimmed === "null") {
|
|
904
|
-
value = null;
|
|
905
|
-
} else if (trimmed === "true") {
|
|
906
|
-
value = true;
|
|
907
|
-
} else if (trimmed === "false") {
|
|
908
|
-
value = false;
|
|
909
|
-
} else {
|
|
910
|
-
const parsed = tryParseJson(trimmed);
|
|
911
|
-
value = parsed !== null ? parsed : trimmed;
|
|
1134
|
+
function collectRawTextFallbacks(node, file) {
|
|
1135
|
+
const fallbacks = /* @__PURE__ */ new Map();
|
|
1136
|
+
for (const tag of ts.getJSDocTags(node)) {
|
|
1137
|
+
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
1138
|
+
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1139
|
+
const commentText = getTagCommentText(tag)?.trim() ?? "";
|
|
1140
|
+
if (commentText === "") continue;
|
|
1141
|
+
const entries = fallbacks.get(tagName) ?? [];
|
|
1142
|
+
entries.push({
|
|
1143
|
+
text: commentText,
|
|
1144
|
+
provenance: provenanceForJSDocTag(tag, file)
|
|
1145
|
+
});
|
|
1146
|
+
fallbacks.set(tagName, entries);
|
|
912
1147
|
}
|
|
913
|
-
return
|
|
914
|
-
kind: "annotation",
|
|
915
|
-
annotationKind: "defaultValue",
|
|
916
|
-
value,
|
|
917
|
-
provenance
|
|
918
|
-
};
|
|
1148
|
+
return fallbacks;
|
|
919
1149
|
}
|
|
920
1150
|
function isMemberTargetDisplayName(text) {
|
|
921
|
-
return
|
|
922
|
-
}
|
|
923
|
-
function parseMemberTargetDisplayName(text) {
|
|
924
|
-
const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
|
|
925
|
-
if (!match?.[1] || !match[2]) return null;
|
|
926
|
-
return { target: match[1], label: match[2].trim() };
|
|
1151
|
+
return (0, import_analysis.parseTagSyntax)("displayName", text).target !== null;
|
|
927
1152
|
}
|
|
928
1153
|
function provenanceForComment(range, sourceFile, file, tagName) {
|
|
929
1154
|
const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
|
|
@@ -935,6 +1160,16 @@ function provenanceForComment(range, sourceFile, file, tagName) {
|
|
|
935
1160
|
tagName: "@" + tagName
|
|
936
1161
|
};
|
|
937
1162
|
}
|
|
1163
|
+
function provenanceForParsedTag(tag, sourceFile, file) {
|
|
1164
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(tag.tagNameSpan.start);
|
|
1165
|
+
return {
|
|
1166
|
+
surface: "tsdoc",
|
|
1167
|
+
file,
|
|
1168
|
+
line: line + 1,
|
|
1169
|
+
column: character,
|
|
1170
|
+
tagName: "@" + tag.normalizedTagName
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
938
1173
|
function provenanceForJSDocTag(tag, file) {
|
|
939
1174
|
const sourceFile = tag.getSourceFile();
|
|
940
1175
|
const { line, character } = sourceFile.getLineAndCharacterOfPosition(tag.getStart());
|
|
@@ -957,12 +1192,15 @@ function getTagCommentText(tag) {
|
|
|
957
1192
|
}
|
|
958
1193
|
|
|
959
1194
|
// src/analyzer/jsdoc-constraints.ts
|
|
1195
|
+
function extractJSDocParseResult(node, file = "", options) {
|
|
1196
|
+
return parseTSDocTags(node, file, options);
|
|
1197
|
+
}
|
|
960
1198
|
function extractJSDocConstraintNodes(node, file = "", options) {
|
|
961
|
-
const result =
|
|
1199
|
+
const result = extractJSDocParseResult(node, file, options);
|
|
962
1200
|
return [...result.constraints];
|
|
963
1201
|
}
|
|
964
1202
|
function extractJSDocAnnotationNodes(node, file = "", options) {
|
|
965
|
-
const result =
|
|
1203
|
+
const result = extractJSDocParseResult(node, file, options);
|
|
966
1204
|
return [...result.annotations];
|
|
967
1205
|
}
|
|
968
1206
|
function extractDefaultValueAnnotation(initializer, file = "") {
|
|
@@ -1011,13 +1249,16 @@ var RESOLVING_TYPE_PLACEHOLDER = {
|
|
|
1011
1249
|
properties: [],
|
|
1012
1250
|
additionalProperties: true
|
|
1013
1251
|
};
|
|
1014
|
-
function makeParseOptions(extensionRegistry, fieldType) {
|
|
1015
|
-
if (extensionRegistry === void 0 && fieldType === void 0) {
|
|
1252
|
+
function makeParseOptions(extensionRegistry, fieldType, checker, subjectType, hostType) {
|
|
1253
|
+
if (extensionRegistry === void 0 && fieldType === void 0 && checker === void 0 && subjectType === void 0 && hostType === void 0) {
|
|
1016
1254
|
return void 0;
|
|
1017
1255
|
}
|
|
1018
1256
|
return {
|
|
1019
1257
|
...extensionRegistry !== void 0 && { extensionRegistry },
|
|
1020
|
-
...fieldType !== void 0 && { fieldType }
|
|
1258
|
+
...fieldType !== void 0 && { fieldType },
|
|
1259
|
+
...checker !== void 0 && { checker },
|
|
1260
|
+
...subjectType !== void 0 && { subjectType },
|
|
1261
|
+
...hostType !== void 0 && { hostType }
|
|
1021
1262
|
};
|
|
1022
1263
|
}
|
|
1023
1264
|
function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
@@ -1025,11 +1266,15 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1025
1266
|
const fields = [];
|
|
1026
1267
|
const fieldLayouts = [];
|
|
1027
1268
|
const typeRegistry = {};
|
|
1028
|
-
const
|
|
1269
|
+
const diagnostics = [];
|
|
1270
|
+
const classType = checker.getTypeAtLocation(classDecl);
|
|
1271
|
+
const classDoc = extractJSDocParseResult(
|
|
1029
1272
|
classDecl,
|
|
1030
1273
|
file,
|
|
1031
|
-
makeParseOptions(extensionRegistry)
|
|
1274
|
+
makeParseOptions(extensionRegistry, void 0, checker, classType, classType)
|
|
1032
1275
|
);
|
|
1276
|
+
const annotations = [...classDoc.annotations];
|
|
1277
|
+
diagnostics.push(...classDoc.diagnostics);
|
|
1033
1278
|
const visiting = /* @__PURE__ */ new Set();
|
|
1034
1279
|
const instanceMethods = [];
|
|
1035
1280
|
const staticMethods = [];
|
|
@@ -1041,6 +1286,8 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1041
1286
|
file,
|
|
1042
1287
|
typeRegistry,
|
|
1043
1288
|
visiting,
|
|
1289
|
+
diagnostics,
|
|
1290
|
+
classType,
|
|
1044
1291
|
extensionRegistry
|
|
1045
1292
|
);
|
|
1046
1293
|
if (fieldNode) {
|
|
@@ -1065,6 +1312,7 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1065
1312
|
fieldLayouts,
|
|
1066
1313
|
typeRegistry,
|
|
1067
1314
|
...annotations.length > 0 && { annotations },
|
|
1315
|
+
...diagnostics.length > 0 && { diagnostics },
|
|
1068
1316
|
instanceMethods,
|
|
1069
1317
|
staticMethods
|
|
1070
1318
|
};
|
|
@@ -1073,11 +1321,15 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
1073
1321
|
const name = interfaceDecl.name.text;
|
|
1074
1322
|
const fields = [];
|
|
1075
1323
|
const typeRegistry = {};
|
|
1076
|
-
const
|
|
1324
|
+
const diagnostics = [];
|
|
1325
|
+
const interfaceType = checker.getTypeAtLocation(interfaceDecl);
|
|
1326
|
+
const interfaceDoc = extractJSDocParseResult(
|
|
1077
1327
|
interfaceDecl,
|
|
1078
1328
|
file,
|
|
1079
|
-
makeParseOptions(extensionRegistry)
|
|
1329
|
+
makeParseOptions(extensionRegistry, void 0, checker, interfaceType, interfaceType)
|
|
1080
1330
|
);
|
|
1331
|
+
const annotations = [...interfaceDoc.annotations];
|
|
1332
|
+
diagnostics.push(...interfaceDoc.diagnostics);
|
|
1081
1333
|
const visiting = /* @__PURE__ */ new Set();
|
|
1082
1334
|
for (const member of interfaceDecl.members) {
|
|
1083
1335
|
if (ts3.isPropertySignature(member)) {
|
|
@@ -1087,6 +1339,8 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
1087
1339
|
file,
|
|
1088
1340
|
typeRegistry,
|
|
1089
1341
|
visiting,
|
|
1342
|
+
diagnostics,
|
|
1343
|
+
interfaceType,
|
|
1090
1344
|
extensionRegistry
|
|
1091
1345
|
);
|
|
1092
1346
|
if (fieldNode) {
|
|
@@ -1101,6 +1355,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
1101
1355
|
fieldLayouts,
|
|
1102
1356
|
typeRegistry,
|
|
1103
1357
|
...annotations.length > 0 && { annotations },
|
|
1358
|
+
...diagnostics.length > 0 && { diagnostics },
|
|
1104
1359
|
instanceMethods: [],
|
|
1105
1360
|
staticMethods: []
|
|
1106
1361
|
};
|
|
@@ -1118,11 +1373,15 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
1118
1373
|
const name = typeAlias.name.text;
|
|
1119
1374
|
const fields = [];
|
|
1120
1375
|
const typeRegistry = {};
|
|
1121
|
-
const
|
|
1376
|
+
const diagnostics = [];
|
|
1377
|
+
const aliasType = checker.getTypeAtLocation(typeAlias);
|
|
1378
|
+
const typeAliasDoc = extractJSDocParseResult(
|
|
1122
1379
|
typeAlias,
|
|
1123
1380
|
file,
|
|
1124
|
-
makeParseOptions(extensionRegistry)
|
|
1381
|
+
makeParseOptions(extensionRegistry, void 0, checker, aliasType, aliasType)
|
|
1125
1382
|
);
|
|
1383
|
+
const annotations = [...typeAliasDoc.annotations];
|
|
1384
|
+
diagnostics.push(...typeAliasDoc.diagnostics);
|
|
1126
1385
|
const visiting = /* @__PURE__ */ new Set();
|
|
1127
1386
|
for (const member of typeAlias.type.members) {
|
|
1128
1387
|
if (ts3.isPropertySignature(member)) {
|
|
@@ -1132,6 +1391,8 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
1132
1391
|
file,
|
|
1133
1392
|
typeRegistry,
|
|
1134
1393
|
visiting,
|
|
1394
|
+
diagnostics,
|
|
1395
|
+
aliasType,
|
|
1135
1396
|
extensionRegistry
|
|
1136
1397
|
);
|
|
1137
1398
|
if (fieldNode) {
|
|
@@ -1147,12 +1408,13 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
1147
1408
|
fieldLayouts: fields.map(() => ({})),
|
|
1148
1409
|
typeRegistry,
|
|
1149
1410
|
...annotations.length > 0 && { annotations },
|
|
1411
|
+
...diagnostics.length > 0 && { diagnostics },
|
|
1150
1412
|
instanceMethods: [],
|
|
1151
1413
|
staticMethods: []
|
|
1152
1414
|
}
|
|
1153
1415
|
};
|
|
1154
1416
|
}
|
|
1155
|
-
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1417
|
+
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
|
|
1156
1418
|
if (!ts3.isIdentifier(prop.name)) {
|
|
1157
1419
|
return null;
|
|
1158
1420
|
}
|
|
@@ -1167,7 +1429,8 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
|
|
|
1167
1429
|
typeRegistry,
|
|
1168
1430
|
visiting,
|
|
1169
1431
|
prop,
|
|
1170
|
-
extensionRegistry
|
|
1432
|
+
extensionRegistry,
|
|
1433
|
+
diagnostics
|
|
1171
1434
|
);
|
|
1172
1435
|
const constraints = [];
|
|
1173
1436
|
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
@@ -1175,13 +1438,15 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
|
|
|
1175
1438
|
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
1176
1439
|
);
|
|
1177
1440
|
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1441
|
+
const docResult = extractJSDocParseResult(
|
|
1442
|
+
prop,
|
|
1443
|
+
file,
|
|
1444
|
+
makeParseOptions(extensionRegistry, type, checker, tsType, hostType)
|
|
1180
1445
|
);
|
|
1446
|
+
constraints.push(...docResult.constraints);
|
|
1447
|
+
diagnostics.push(...docResult.diagnostics);
|
|
1181
1448
|
let annotations = [];
|
|
1182
|
-
annotations.push(
|
|
1183
|
-
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
1184
|
-
);
|
|
1449
|
+
annotations.push(...docResult.annotations);
|
|
1185
1450
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
1186
1451
|
if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
|
|
1187
1452
|
annotations.push(defaultAnnotation);
|
|
@@ -1197,7 +1462,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
|
|
|
1197
1462
|
provenance
|
|
1198
1463
|
};
|
|
1199
1464
|
}
|
|
1200
|
-
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1465
|
+
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
|
|
1201
1466
|
if (!ts3.isIdentifier(prop.name)) {
|
|
1202
1467
|
return null;
|
|
1203
1468
|
}
|
|
@@ -1212,7 +1477,8 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
1212
1477
|
typeRegistry,
|
|
1213
1478
|
visiting,
|
|
1214
1479
|
prop,
|
|
1215
|
-
extensionRegistry
|
|
1480
|
+
extensionRegistry,
|
|
1481
|
+
diagnostics
|
|
1216
1482
|
);
|
|
1217
1483
|
const constraints = [];
|
|
1218
1484
|
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
@@ -1220,13 +1486,15 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
1220
1486
|
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
1221
1487
|
);
|
|
1222
1488
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1489
|
+
const docResult = extractJSDocParseResult(
|
|
1490
|
+
prop,
|
|
1491
|
+
file,
|
|
1492
|
+
makeParseOptions(extensionRegistry, type, checker, tsType, hostType)
|
|
1225
1493
|
);
|
|
1494
|
+
constraints.push(...docResult.constraints);
|
|
1495
|
+
diagnostics.push(...docResult.diagnostics);
|
|
1226
1496
|
let annotations = [];
|
|
1227
|
-
annotations.push(
|
|
1228
|
-
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
1229
|
-
);
|
|
1497
|
+
annotations.push(...docResult.annotations);
|
|
1230
1498
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
1231
1499
|
return {
|
|
1232
1500
|
kind: "field",
|
|
@@ -1355,7 +1623,7 @@ function getTypeNodeRegistrationName(typeNode) {
|
|
|
1355
1623
|
}
|
|
1356
1624
|
return null;
|
|
1357
1625
|
}
|
|
1358
|
-
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1626
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
1359
1627
|
const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
|
|
1360
1628
|
if (customType) {
|
|
1361
1629
|
return customType;
|
|
@@ -1367,7 +1635,8 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
1367
1635
|
typeRegistry,
|
|
1368
1636
|
visiting,
|
|
1369
1637
|
sourceNode,
|
|
1370
|
-
extensionRegistry
|
|
1638
|
+
extensionRegistry,
|
|
1639
|
+
diagnostics
|
|
1371
1640
|
);
|
|
1372
1641
|
if (primitiveAlias) {
|
|
1373
1642
|
return primitiveAlias;
|
|
@@ -1410,7 +1679,8 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
1410
1679
|
typeRegistry,
|
|
1411
1680
|
visiting,
|
|
1412
1681
|
sourceNode,
|
|
1413
|
-
extensionRegistry
|
|
1682
|
+
extensionRegistry,
|
|
1683
|
+
diagnostics
|
|
1414
1684
|
);
|
|
1415
1685
|
}
|
|
1416
1686
|
if (checker.isArrayType(type)) {
|
|
@@ -1421,15 +1691,24 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
1421
1691
|
typeRegistry,
|
|
1422
1692
|
visiting,
|
|
1423
1693
|
sourceNode,
|
|
1424
|
-
extensionRegistry
|
|
1694
|
+
extensionRegistry,
|
|
1695
|
+
diagnostics
|
|
1425
1696
|
);
|
|
1426
1697
|
}
|
|
1427
1698
|
if (isObjectType(type)) {
|
|
1428
|
-
return resolveObjectType(
|
|
1699
|
+
return resolveObjectType(
|
|
1700
|
+
type,
|
|
1701
|
+
checker,
|
|
1702
|
+
file,
|
|
1703
|
+
typeRegistry,
|
|
1704
|
+
visiting,
|
|
1705
|
+
extensionRegistry,
|
|
1706
|
+
diagnostics
|
|
1707
|
+
);
|
|
1429
1708
|
}
|
|
1430
1709
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1431
1710
|
}
|
|
1432
|
-
function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1711
|
+
function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
1433
1712
|
if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
|
|
1434
1713
|
return null;
|
|
1435
1714
|
}
|
|
@@ -1457,7 +1736,8 @@ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiti
|
|
|
1457
1736
|
file,
|
|
1458
1737
|
typeRegistry,
|
|
1459
1738
|
visiting,
|
|
1460
|
-
extensionRegistry
|
|
1739
|
+
extensionRegistry,
|
|
1740
|
+
diagnostics
|
|
1461
1741
|
),
|
|
1462
1742
|
...constraints.length > 0 && { constraints },
|
|
1463
1743
|
...annotations.length > 0 && { annotations },
|
|
@@ -1484,7 +1764,7 @@ function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
|
|
|
1484
1764
|
const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
|
|
1485
1765
|
return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
|
|
1486
1766
|
}
|
|
1487
|
-
function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1767
|
+
function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
1488
1768
|
const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
1489
1769
|
if (nestedAliasDecl !== void 0) {
|
|
1490
1770
|
return resolveAliasedPrimitiveTarget(
|
|
@@ -1493,12 +1773,22 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
|
|
|
1493
1773
|
file,
|
|
1494
1774
|
typeRegistry,
|
|
1495
1775
|
visiting,
|
|
1496
|
-
extensionRegistry
|
|
1776
|
+
extensionRegistry,
|
|
1777
|
+
diagnostics
|
|
1497
1778
|
);
|
|
1498
1779
|
}
|
|
1499
|
-
return resolveTypeNode(
|
|
1780
|
+
return resolveTypeNode(
|
|
1781
|
+
type,
|
|
1782
|
+
checker,
|
|
1783
|
+
file,
|
|
1784
|
+
typeRegistry,
|
|
1785
|
+
visiting,
|
|
1786
|
+
void 0,
|
|
1787
|
+
extensionRegistry,
|
|
1788
|
+
diagnostics
|
|
1789
|
+
);
|
|
1500
1790
|
}
|
|
1501
|
-
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1791
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
1502
1792
|
const typeName = getNamedTypeName(type);
|
|
1503
1793
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
1504
1794
|
if (typeName && typeName in typeRegistry) {
|
|
@@ -1588,7 +1878,8 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1588
1878
|
typeRegistry,
|
|
1589
1879
|
visiting,
|
|
1590
1880
|
nonNullMembers[0].sourceNode ?? sourceNode,
|
|
1591
|
-
extensionRegistry
|
|
1881
|
+
extensionRegistry,
|
|
1882
|
+
diagnostics
|
|
1592
1883
|
);
|
|
1593
1884
|
const result = hasNull ? {
|
|
1594
1885
|
kind: "union",
|
|
@@ -1604,7 +1895,8 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1604
1895
|
typeRegistry,
|
|
1605
1896
|
visiting,
|
|
1606
1897
|
memberSourceNode ?? sourceNode,
|
|
1607
|
-
extensionRegistry
|
|
1898
|
+
extensionRegistry,
|
|
1899
|
+
diagnostics
|
|
1608
1900
|
)
|
|
1609
1901
|
);
|
|
1610
1902
|
if (hasNull) {
|
|
@@ -1612,7 +1904,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1612
1904
|
}
|
|
1613
1905
|
return registerNamed({ kind: "union", members });
|
|
1614
1906
|
}
|
|
1615
|
-
function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1907
|
+
function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
1616
1908
|
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
1617
1909
|
const elementType = typeArgs?.[0];
|
|
1618
1910
|
const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
|
|
@@ -1623,11 +1915,12 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1623
1915
|
typeRegistry,
|
|
1624
1916
|
visiting,
|
|
1625
1917
|
elementSourceNode,
|
|
1626
|
-
extensionRegistry
|
|
1918
|
+
extensionRegistry,
|
|
1919
|
+
diagnostics
|
|
1627
1920
|
) : { kind: "primitive", primitiveKind: "string" };
|
|
1628
1921
|
return { kind: "array", items };
|
|
1629
1922
|
}
|
|
1630
|
-
function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1923
|
+
function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
1631
1924
|
if (type.getProperties().length > 0) {
|
|
1632
1925
|
return null;
|
|
1633
1926
|
}
|
|
@@ -1642,7 +1935,8 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, exten
|
|
|
1642
1935
|
typeRegistry,
|
|
1643
1936
|
visiting,
|
|
1644
1937
|
void 0,
|
|
1645
|
-
extensionRegistry
|
|
1938
|
+
extensionRegistry,
|
|
1939
|
+
diagnostics
|
|
1646
1940
|
);
|
|
1647
1941
|
return { kind: "record", valueType };
|
|
1648
1942
|
}
|
|
@@ -1671,7 +1965,7 @@ function typeNodeContainsReference(type, targetName) {
|
|
|
1671
1965
|
}
|
|
1672
1966
|
}
|
|
1673
1967
|
}
|
|
1674
|
-
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1968
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
1675
1969
|
const typeName = getNamedTypeName(type);
|
|
1676
1970
|
const namedTypeName = typeName ?? void 0;
|
|
1677
1971
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
@@ -1708,7 +2002,8 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
1708
2002
|
file,
|
|
1709
2003
|
typeRegistry,
|
|
1710
2004
|
visiting,
|
|
1711
|
-
extensionRegistry
|
|
2005
|
+
extensionRegistry,
|
|
2006
|
+
diagnostics
|
|
1712
2007
|
);
|
|
1713
2008
|
if (recordNode) {
|
|
1714
2009
|
visiting.delete(type);
|
|
@@ -1736,6 +2031,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
1736
2031
|
file,
|
|
1737
2032
|
typeRegistry,
|
|
1738
2033
|
visiting,
|
|
2034
|
+
diagnostics ?? [],
|
|
1739
2035
|
extensionRegistry
|
|
1740
2036
|
);
|
|
1741
2037
|
for (const prop of type.getProperties()) {
|
|
@@ -1750,7 +2046,8 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
1750
2046
|
typeRegistry,
|
|
1751
2047
|
visiting,
|
|
1752
2048
|
declaration,
|
|
1753
|
-
extensionRegistry
|
|
2049
|
+
extensionRegistry,
|
|
2050
|
+
diagnostics
|
|
1754
2051
|
);
|
|
1755
2052
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
1756
2053
|
properties.push({
|
|
@@ -1780,7 +2077,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
1780
2077
|
}
|
|
1781
2078
|
return objectNode;
|
|
1782
2079
|
}
|
|
1783
|
-
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2080
|
+
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, diagnostics, extensionRegistry) {
|
|
1784
2081
|
const symbols = [type.getSymbol(), type.aliasSymbol].filter(
|
|
1785
2082
|
(s) => s?.declarations != null && s.declarations.length > 0
|
|
1786
2083
|
);
|
|
@@ -1790,6 +2087,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1790
2087
|
const classDecl = declarations.find(ts3.isClassDeclaration);
|
|
1791
2088
|
if (classDecl) {
|
|
1792
2089
|
const map = /* @__PURE__ */ new Map();
|
|
2090
|
+
const hostType = checker.getTypeAtLocation(classDecl);
|
|
1793
2091
|
for (const member of classDecl.members) {
|
|
1794
2092
|
if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name)) {
|
|
1795
2093
|
const fieldNode = analyzeFieldToIR(
|
|
@@ -1798,6 +2096,8 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1798
2096
|
file,
|
|
1799
2097
|
typeRegistry,
|
|
1800
2098
|
visiting,
|
|
2099
|
+
diagnostics,
|
|
2100
|
+
hostType,
|
|
1801
2101
|
extensionRegistry
|
|
1802
2102
|
);
|
|
1803
2103
|
if (fieldNode) {
|
|
@@ -1819,6 +2119,8 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1819
2119
|
file,
|
|
1820
2120
|
typeRegistry,
|
|
1821
2121
|
visiting,
|
|
2122
|
+
checker.getTypeAtLocation(interfaceDecl),
|
|
2123
|
+
diagnostics,
|
|
1822
2124
|
extensionRegistry
|
|
1823
2125
|
);
|
|
1824
2126
|
}
|
|
@@ -1830,6 +2132,8 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1830
2132
|
file,
|
|
1831
2133
|
typeRegistry,
|
|
1832
2134
|
visiting,
|
|
2135
|
+
checker.getTypeAtLocation(typeAliasDecl),
|
|
2136
|
+
diagnostics,
|
|
1833
2137
|
extensionRegistry
|
|
1834
2138
|
);
|
|
1835
2139
|
}
|
|
@@ -1879,7 +2183,7 @@ function isNullishTypeNode(typeNode) {
|
|
|
1879
2183
|
}
|
|
1880
2184
|
return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
|
|
1881
2185
|
}
|
|
1882
|
-
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2186
|
+
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, hostType, diagnostics, extensionRegistry) {
|
|
1883
2187
|
const map = /* @__PURE__ */ new Map();
|
|
1884
2188
|
for (const member of members) {
|
|
1885
2189
|
if (ts3.isPropertySignature(member)) {
|
|
@@ -1889,6 +2193,8 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, e
|
|
|
1889
2193
|
file,
|
|
1890
2194
|
typeRegistry,
|
|
1891
2195
|
visiting,
|
|
2196
|
+
diagnostics,
|
|
2197
|
+
hostType,
|
|
1892
2198
|
extensionRegistry
|
|
1893
2199
|
);
|
|
1894
2200
|
if (fieldNode) {
|
|
@@ -2753,760 +3059,42 @@ function generateUiSchemaFromIR(ir) {
|
|
|
2753
3059
|
}
|
|
2754
3060
|
|
|
2755
3061
|
// src/validate/constraint-validator.ts
|
|
2756
|
-
var
|
|
2757
|
-
function
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
}
|
|
2766
|
-
function addTypeMismatch(ctx, message, primary) {
|
|
2767
|
-
ctx.diagnostics.push({
|
|
2768
|
-
code: "TYPE_MISMATCH",
|
|
2769
|
-
message,
|
|
2770
|
-
severity: "error",
|
|
2771
|
-
primaryLocation: primary,
|
|
2772
|
-
relatedLocations: []
|
|
2773
|
-
});
|
|
2774
|
-
}
|
|
2775
|
-
function addUnknownExtension(ctx, message, primary) {
|
|
2776
|
-
ctx.diagnostics.push({
|
|
2777
|
-
code: "UNKNOWN_EXTENSION",
|
|
2778
|
-
message,
|
|
2779
|
-
severity: "warning",
|
|
2780
|
-
primaryLocation: primary,
|
|
2781
|
-
relatedLocations: []
|
|
2782
|
-
});
|
|
2783
|
-
}
|
|
2784
|
-
function addUnknownPathTarget(ctx, message, primary) {
|
|
2785
|
-
ctx.diagnostics.push({
|
|
2786
|
-
code: "UNKNOWN_PATH_TARGET",
|
|
2787
|
-
message,
|
|
2788
|
-
severity: "error",
|
|
2789
|
-
primaryLocation: primary,
|
|
2790
|
-
relatedLocations: []
|
|
2791
|
-
});
|
|
2792
|
-
}
|
|
2793
|
-
function addConstraintBroadening(ctx, message, primary, related) {
|
|
2794
|
-
ctx.diagnostics.push({
|
|
2795
|
-
code: "CONSTRAINT_BROADENING",
|
|
2796
|
-
message,
|
|
2797
|
-
severity: "error",
|
|
2798
|
-
primaryLocation: primary,
|
|
2799
|
-
relatedLocations: [related]
|
|
2800
|
-
});
|
|
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
|
-
}
|
|
2809
|
-
function findNumeric(constraints, constraintKind) {
|
|
2810
|
-
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
2811
|
-
}
|
|
2812
|
-
function findLength(constraints, constraintKind) {
|
|
2813
|
-
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
2814
|
-
}
|
|
2815
|
-
function findAllowedMembers(constraints) {
|
|
2816
|
-
return constraints.filter(
|
|
2817
|
-
(c) => c.constraintKind === "allowedMembers"
|
|
2818
|
-
);
|
|
2819
|
-
}
|
|
2820
|
-
function findConstConstraints(constraints) {
|
|
2821
|
-
return constraints.filter(
|
|
2822
|
-
(c) => c.constraintKind === "const"
|
|
2823
|
-
);
|
|
2824
|
-
}
|
|
2825
|
-
function jsonValueEquals(left, right) {
|
|
2826
|
-
if (left === right) {
|
|
2827
|
-
return true;
|
|
2828
|
-
}
|
|
2829
|
-
if (Array.isArray(left) || Array.isArray(right)) {
|
|
2830
|
-
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
|
2831
|
-
return false;
|
|
2832
|
-
}
|
|
2833
|
-
return left.every((item, index) => jsonValueEquals(item, right[index]));
|
|
2834
|
-
}
|
|
2835
|
-
if (isJsonObject(left) || isJsonObject(right)) {
|
|
2836
|
-
if (!isJsonObject(left) || !isJsonObject(right)) {
|
|
2837
|
-
return false;
|
|
2838
|
-
}
|
|
2839
|
-
const leftKeys = Object.keys(left).sort();
|
|
2840
|
-
const rightKeys = Object.keys(right).sort();
|
|
2841
|
-
if (leftKeys.length !== rightKeys.length) {
|
|
2842
|
-
return false;
|
|
2843
|
-
}
|
|
2844
|
-
return leftKeys.every((key, index) => {
|
|
2845
|
-
const rightKey = rightKeys[index];
|
|
2846
|
-
if (rightKey !== key) {
|
|
2847
|
-
return false;
|
|
2848
|
-
}
|
|
2849
|
-
const leftValue = left[key];
|
|
2850
|
-
const rightValue = right[rightKey];
|
|
2851
|
-
return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
|
|
2852
|
-
});
|
|
2853
|
-
}
|
|
2854
|
-
return false;
|
|
2855
|
-
}
|
|
2856
|
-
function isJsonObject(value) {
|
|
2857
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2858
|
-
}
|
|
2859
|
-
function isOrderedBoundConstraint(constraint) {
|
|
2860
|
-
return constraint.constraintKind === "minimum" || constraint.constraintKind === "exclusiveMinimum" || constraint.constraintKind === "minLength" || constraint.constraintKind === "minItems" || constraint.constraintKind === "maximum" || constraint.constraintKind === "exclusiveMaximum" || constraint.constraintKind === "maxLength" || constraint.constraintKind === "maxItems";
|
|
2861
|
-
}
|
|
2862
|
-
function pathKey(constraint) {
|
|
2863
|
-
return constraint.path?.segments.join(".") ?? "";
|
|
2864
|
-
}
|
|
2865
|
-
function orderedBoundFamily(kind) {
|
|
2866
|
-
switch (kind) {
|
|
2867
|
-
case "minimum":
|
|
2868
|
-
case "exclusiveMinimum":
|
|
2869
|
-
return "numeric-lower";
|
|
2870
|
-
case "maximum":
|
|
2871
|
-
case "exclusiveMaximum":
|
|
2872
|
-
return "numeric-upper";
|
|
2873
|
-
case "minLength":
|
|
2874
|
-
return "minLength";
|
|
2875
|
-
case "minItems":
|
|
2876
|
-
return "minItems";
|
|
2877
|
-
case "maxLength":
|
|
2878
|
-
return "maxLength";
|
|
2879
|
-
case "maxItems":
|
|
2880
|
-
return "maxItems";
|
|
2881
|
-
default: {
|
|
2882
|
-
const _exhaustive = kind;
|
|
2883
|
-
return _exhaustive;
|
|
2884
|
-
}
|
|
2885
|
-
}
|
|
2886
|
-
}
|
|
2887
|
-
function isNumericLowerKind(kind) {
|
|
2888
|
-
return kind === "minimum" || kind === "exclusiveMinimum";
|
|
2889
|
-
}
|
|
2890
|
-
function isNumericUpperKind(kind) {
|
|
2891
|
-
return kind === "maximum" || kind === "exclusiveMaximum";
|
|
2892
|
-
}
|
|
2893
|
-
function describeConstraintTag(constraint) {
|
|
2894
|
-
return `@${constraint.constraintKind}`;
|
|
2895
|
-
}
|
|
2896
|
-
function compareConstraintStrength(current, previous) {
|
|
2897
|
-
const family = orderedBoundFamily(current.constraintKind);
|
|
2898
|
-
if (family === "numeric-lower") {
|
|
2899
|
-
if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
|
|
2900
|
-
throw new Error("numeric-lower family received non-numeric lower-bound constraint");
|
|
2901
|
-
}
|
|
2902
|
-
if (current.value !== previous.value) {
|
|
2903
|
-
return current.value > previous.value ? 1 : -1;
|
|
2904
|
-
}
|
|
2905
|
-
if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
|
|
2906
|
-
return 1;
|
|
2907
|
-
}
|
|
2908
|
-
if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
|
|
2909
|
-
return -1;
|
|
2910
|
-
}
|
|
2911
|
-
return 0;
|
|
2912
|
-
}
|
|
2913
|
-
if (family === "numeric-upper") {
|
|
2914
|
-
if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
|
|
2915
|
-
throw new Error("numeric-upper family received non-numeric upper-bound constraint");
|
|
2916
|
-
}
|
|
2917
|
-
if (current.value !== previous.value) {
|
|
2918
|
-
return current.value < previous.value ? 1 : -1;
|
|
2919
|
-
}
|
|
2920
|
-
if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
|
|
2921
|
-
return 1;
|
|
2922
|
-
}
|
|
2923
|
-
if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
|
|
2924
|
-
return -1;
|
|
2925
|
-
}
|
|
2926
|
-
return 0;
|
|
2927
|
-
}
|
|
2928
|
-
switch (family) {
|
|
2929
|
-
case "minLength":
|
|
2930
|
-
case "minItems":
|
|
2931
|
-
if (current.value === previous.value) {
|
|
2932
|
-
return 0;
|
|
2933
|
-
}
|
|
2934
|
-
return current.value > previous.value ? 1 : -1;
|
|
2935
|
-
case "maxLength":
|
|
2936
|
-
case "maxItems":
|
|
2937
|
-
if (current.value === previous.value) {
|
|
2938
|
-
return 0;
|
|
2939
|
-
}
|
|
2940
|
-
return current.value < previous.value ? 1 : -1;
|
|
2941
|
-
default: {
|
|
2942
|
-
const _exhaustive = family;
|
|
2943
|
-
return _exhaustive;
|
|
2944
|
-
}
|
|
2945
|
-
}
|
|
2946
|
-
}
|
|
2947
|
-
function checkConstraintBroadening(ctx, fieldName, constraints) {
|
|
2948
|
-
const strongestByKey = /* @__PURE__ */ new Map();
|
|
2949
|
-
for (const constraint of constraints) {
|
|
2950
|
-
if (!isOrderedBoundConstraint(constraint)) {
|
|
2951
|
-
continue;
|
|
2952
|
-
}
|
|
2953
|
-
const key = `${orderedBoundFamily(constraint.constraintKind)}:${pathKey(constraint)}`;
|
|
2954
|
-
const previous = strongestByKey.get(key);
|
|
2955
|
-
if (previous === void 0) {
|
|
2956
|
-
strongestByKey.set(key, constraint);
|
|
2957
|
-
continue;
|
|
2958
|
-
}
|
|
2959
|
-
const strength = compareConstraintStrength(constraint, previous);
|
|
2960
|
-
if (strength < 0) {
|
|
2961
|
-
const displayFieldName = formatPathTargetFieldName(
|
|
2962
|
-
fieldName,
|
|
2963
|
-
constraint.path?.segments ?? []
|
|
2964
|
-
);
|
|
2965
|
-
addConstraintBroadening(
|
|
2966
|
-
ctx,
|
|
2967
|
-
`Field "${displayFieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
|
|
2968
|
-
constraint.provenance,
|
|
2969
|
-
previous.provenance
|
|
2970
|
-
);
|
|
2971
|
-
continue;
|
|
2972
|
-
}
|
|
2973
|
-
if (strength <= 0) {
|
|
2974
|
-
continue;
|
|
2975
|
-
}
|
|
2976
|
-
strongestByKey.set(key, constraint);
|
|
2977
|
-
}
|
|
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
|
-
}
|
|
3085
|
-
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
3086
|
-
const min = findNumeric(constraints, "minimum");
|
|
3087
|
-
const max = findNumeric(constraints, "maximum");
|
|
3088
|
-
const exMin = findNumeric(constraints, "exclusiveMinimum");
|
|
3089
|
-
const exMax = findNumeric(constraints, "exclusiveMaximum");
|
|
3090
|
-
if (min !== void 0 && max !== void 0 && min.value > max.value) {
|
|
3091
|
-
addContradiction(
|
|
3092
|
-
ctx,
|
|
3093
|
-
`Field "${fieldName}": minimum (${String(min.value)}) is greater than maximum (${String(max.value)})`,
|
|
3094
|
-
min.provenance,
|
|
3095
|
-
max.provenance
|
|
3096
|
-
);
|
|
3097
|
-
}
|
|
3098
|
-
if (exMin !== void 0 && max !== void 0 && exMin.value >= max.value) {
|
|
3099
|
-
addContradiction(
|
|
3100
|
-
ctx,
|
|
3101
|
-
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to maximum (${String(max.value)})`,
|
|
3102
|
-
exMin.provenance,
|
|
3103
|
-
max.provenance
|
|
3104
|
-
);
|
|
3105
|
-
}
|
|
3106
|
-
if (min !== void 0 && exMax !== void 0 && min.value >= exMax.value) {
|
|
3107
|
-
addContradiction(
|
|
3108
|
-
ctx,
|
|
3109
|
-
`Field "${fieldName}": minimum (${String(min.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
3110
|
-
min.provenance,
|
|
3111
|
-
exMax.provenance
|
|
3112
|
-
);
|
|
3113
|
-
}
|
|
3114
|
-
if (exMin !== void 0 && exMax !== void 0 && exMin.value >= exMax.value) {
|
|
3115
|
-
addContradiction(
|
|
3116
|
-
ctx,
|
|
3117
|
-
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
3118
|
-
exMin.provenance,
|
|
3119
|
-
exMax.provenance
|
|
3120
|
-
);
|
|
3121
|
-
}
|
|
3122
|
-
}
|
|
3123
|
-
function checkLengthContradictions(ctx, fieldName, constraints) {
|
|
3124
|
-
const minLen = findLength(constraints, "minLength");
|
|
3125
|
-
const maxLen = findLength(constraints, "maxLength");
|
|
3126
|
-
if (minLen !== void 0 && maxLen !== void 0 && minLen.value > maxLen.value) {
|
|
3127
|
-
addContradiction(
|
|
3128
|
-
ctx,
|
|
3129
|
-
`Field "${fieldName}": minLength (${String(minLen.value)}) is greater than maxLength (${String(maxLen.value)})`,
|
|
3130
|
-
minLen.provenance,
|
|
3131
|
-
maxLen.provenance
|
|
3132
|
-
);
|
|
3133
|
-
}
|
|
3134
|
-
const minItems = findLength(constraints, "minItems");
|
|
3135
|
-
const maxItems = findLength(constraints, "maxItems");
|
|
3136
|
-
if (minItems !== void 0 && maxItems !== void 0 && minItems.value > maxItems.value) {
|
|
3137
|
-
addContradiction(
|
|
3138
|
-
ctx,
|
|
3139
|
-
`Field "${fieldName}": minItems (${String(minItems.value)}) is greater than maxItems (${String(maxItems.value)})`,
|
|
3140
|
-
minItems.provenance,
|
|
3141
|
-
maxItems.provenance
|
|
3142
|
-
);
|
|
3143
|
-
}
|
|
3144
|
-
}
|
|
3145
|
-
function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
|
|
3146
|
-
const members = findAllowedMembers(constraints);
|
|
3147
|
-
if (members.length < 2) return;
|
|
3148
|
-
const firstSet = new Set(members[0]?.members ?? []);
|
|
3149
|
-
for (let i = 1; i < members.length; i++) {
|
|
3150
|
-
const current = members[i];
|
|
3151
|
-
if (current === void 0) continue;
|
|
3152
|
-
for (const m of firstSet) {
|
|
3153
|
-
if (!current.members.includes(m)) {
|
|
3154
|
-
firstSet.delete(m);
|
|
3155
|
-
}
|
|
3156
|
-
}
|
|
3157
|
-
}
|
|
3158
|
-
if (firstSet.size === 0) {
|
|
3159
|
-
const first = members[0];
|
|
3160
|
-
const second = members[1];
|
|
3161
|
-
if (first !== void 0 && second !== void 0) {
|
|
3162
|
-
addContradiction(
|
|
3163
|
-
ctx,
|
|
3164
|
-
`Field "${fieldName}": allowedMembers constraints have an empty intersection (no valid values remain)`,
|
|
3165
|
-
first.provenance,
|
|
3166
|
-
second.provenance
|
|
3167
|
-
);
|
|
3168
|
-
}
|
|
3169
|
-
}
|
|
3170
|
-
}
|
|
3171
|
-
function checkConstContradictions(ctx, fieldName, constraints) {
|
|
3172
|
-
const constConstraints = findConstConstraints(constraints);
|
|
3173
|
-
if (constConstraints.length < 2) return;
|
|
3174
|
-
const first = constConstraints[0];
|
|
3175
|
-
if (first === void 0) return;
|
|
3176
|
-
for (let i = 1; i < constConstraints.length; i++) {
|
|
3177
|
-
const current = constConstraints[i];
|
|
3178
|
-
if (current === void 0) continue;
|
|
3179
|
-
if (jsonValueEquals(first.value, current.value)) {
|
|
3180
|
-
continue;
|
|
3181
|
-
}
|
|
3182
|
-
addContradiction(
|
|
3183
|
-
ctx,
|
|
3184
|
-
`Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
|
|
3185
|
-
first.provenance,
|
|
3186
|
-
current.provenance
|
|
3187
|
-
);
|
|
3188
|
-
}
|
|
3189
|
-
}
|
|
3190
|
-
function typeLabel(type) {
|
|
3191
|
-
switch (type.kind) {
|
|
3192
|
-
case "primitive":
|
|
3193
|
-
return type.primitiveKind;
|
|
3194
|
-
case "enum":
|
|
3195
|
-
return "enum";
|
|
3196
|
-
case "array":
|
|
3197
|
-
return "array";
|
|
3198
|
-
case "object":
|
|
3199
|
-
return "object";
|
|
3200
|
-
case "record":
|
|
3201
|
-
return "record";
|
|
3202
|
-
case "union":
|
|
3203
|
-
return "union";
|
|
3204
|
-
case "reference":
|
|
3205
|
-
return `reference(${type.name})`;
|
|
3206
|
-
case "dynamic":
|
|
3207
|
-
return `dynamic(${type.dynamicKind})`;
|
|
3208
|
-
case "custom":
|
|
3209
|
-
return `custom(${type.typeId})`;
|
|
3210
|
-
default: {
|
|
3211
|
-
const _exhaustive = type;
|
|
3212
|
-
return String(_exhaustive);
|
|
3213
|
-
}
|
|
3214
|
-
}
|
|
3215
|
-
}
|
|
3216
|
-
function dereferenceType(ctx, type) {
|
|
3217
|
-
let current = type;
|
|
3218
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3219
|
-
while (current.kind === "reference") {
|
|
3220
|
-
if (seen.has(current.name)) {
|
|
3221
|
-
return current;
|
|
3222
|
-
}
|
|
3223
|
-
seen.add(current.name);
|
|
3224
|
-
const definition = ctx.typeRegistry[current.name];
|
|
3225
|
-
if (definition === void 0) {
|
|
3226
|
-
return current;
|
|
3227
|
-
}
|
|
3228
|
-
current = definition.type;
|
|
3229
|
-
}
|
|
3230
|
-
return current;
|
|
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
|
-
}
|
|
3252
|
-
function resolvePathTargetType(ctx, type, segments) {
|
|
3253
|
-
const effectiveType = dereferenceType(ctx, type);
|
|
3254
|
-
if (segments.length === 0) {
|
|
3255
|
-
return { kind: "resolved", type: effectiveType };
|
|
3256
|
-
}
|
|
3257
|
-
if (effectiveType.kind === "array") {
|
|
3258
|
-
return resolvePathTargetType(ctx, effectiveType.items, segments);
|
|
3259
|
-
}
|
|
3260
|
-
if (effectiveType.kind === "object") {
|
|
3261
|
-
const [segment, ...rest] = segments;
|
|
3262
|
-
if (segment === void 0) {
|
|
3263
|
-
throw new Error("Invariant violation: object path traversal requires a segment");
|
|
3264
|
-
}
|
|
3265
|
-
const property = effectiveType.properties.find((prop) => prop.name === segment);
|
|
3266
|
-
if (property === void 0) {
|
|
3267
|
-
return { kind: "missing-property", segment };
|
|
3268
|
-
}
|
|
3269
|
-
return resolvePathTargetType(ctx, property.type, rest);
|
|
3270
|
-
}
|
|
3271
|
-
return { kind: "unresolvable", type: effectiveType };
|
|
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
|
-
}
|
|
3294
|
-
function formatPathTargetFieldName(fieldName, path2) {
|
|
3295
|
-
return path2.length === 0 ? fieldName : `${fieldName}.${path2.join(".")}`;
|
|
3296
|
-
}
|
|
3297
|
-
function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
3298
|
-
const effectiveType = dereferenceType(ctx, type);
|
|
3299
|
-
const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
|
|
3300
|
-
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
3301
|
-
const isArray = effectiveType.kind === "array";
|
|
3302
|
-
const isEnum = effectiveType.kind === "enum";
|
|
3303
|
-
const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
|
|
3304
|
-
const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
|
|
3305
|
-
const label = typeLabel(effectiveType);
|
|
3306
|
-
const ck = constraint.constraintKind;
|
|
3307
|
-
switch (ck) {
|
|
3308
|
-
case "minimum":
|
|
3309
|
-
case "maximum":
|
|
3310
|
-
case "exclusiveMinimum":
|
|
3311
|
-
case "exclusiveMaximum":
|
|
3312
|
-
case "multipleOf": {
|
|
3313
|
-
if (!isNumber) {
|
|
3314
|
-
addTypeMismatch(
|
|
3315
|
-
ctx,
|
|
3316
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
|
|
3317
|
-
constraint.provenance
|
|
3318
|
-
);
|
|
3319
|
-
}
|
|
3320
|
-
break;
|
|
3321
|
-
}
|
|
3322
|
-
case "minLength":
|
|
3323
|
-
case "maxLength":
|
|
3324
|
-
case "pattern": {
|
|
3325
|
-
if (!isString && !isStringArray) {
|
|
3326
|
-
addTypeMismatch(
|
|
3327
|
-
ctx,
|
|
3328
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
|
|
3329
|
-
constraint.provenance
|
|
3330
|
-
);
|
|
3331
|
-
}
|
|
3332
|
-
break;
|
|
3333
|
-
}
|
|
3334
|
-
case "minItems":
|
|
3335
|
-
case "maxItems":
|
|
3336
|
-
case "uniqueItems": {
|
|
3337
|
-
if (!isArray) {
|
|
3338
|
-
addTypeMismatch(
|
|
3339
|
-
ctx,
|
|
3340
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
|
|
3341
|
-
constraint.provenance
|
|
3342
|
-
);
|
|
3343
|
-
}
|
|
3344
|
-
break;
|
|
3345
|
-
}
|
|
3346
|
-
case "allowedMembers": {
|
|
3347
|
-
if (!isEnum) {
|
|
3348
|
-
addTypeMismatch(
|
|
3349
|
-
ctx,
|
|
3350
|
-
`Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
|
|
3351
|
-
constraint.provenance
|
|
3352
|
-
);
|
|
3353
|
-
}
|
|
3354
|
-
break;
|
|
3355
|
-
}
|
|
3356
|
-
case "const": {
|
|
3357
|
-
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
|
|
3358
|
-
effectiveType.primitiveKind
|
|
3359
|
-
) || effectiveType.kind === "enum";
|
|
3360
|
-
if (!isPrimitiveConstType) {
|
|
3361
|
-
addTypeMismatch(
|
|
3362
|
-
ctx,
|
|
3363
|
-
`Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
|
|
3364
|
-
constraint.provenance
|
|
3365
|
-
);
|
|
3366
|
-
break;
|
|
3367
|
-
}
|
|
3368
|
-
if (effectiveType.kind === "primitive") {
|
|
3369
|
-
const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
|
|
3370
|
-
const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
|
|
3371
|
-
if (valueType !== expectedValueType) {
|
|
3372
|
-
addTypeMismatch(
|
|
3373
|
-
ctx,
|
|
3374
|
-
`Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
|
|
3375
|
-
constraint.provenance
|
|
3376
|
-
);
|
|
3377
|
-
}
|
|
3378
|
-
break;
|
|
3379
|
-
}
|
|
3380
|
-
const memberValues = effectiveType.members.map((member) => member.value);
|
|
3381
|
-
if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
|
|
3382
|
-
addTypeMismatch(
|
|
3383
|
-
ctx,
|
|
3384
|
-
`Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
|
|
3385
|
-
constraint.provenance
|
|
3386
|
-
);
|
|
3387
|
-
}
|
|
3388
|
-
break;
|
|
3389
|
-
}
|
|
3390
|
-
case "custom": {
|
|
3391
|
-
checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
|
|
3392
|
-
break;
|
|
3393
|
-
}
|
|
3394
|
-
default: {
|
|
3395
|
-
const _exhaustive = constraint;
|
|
3396
|
-
throw new Error(
|
|
3397
|
-
`Unhandled constraint kind: ${_exhaustive.constraintKind}`
|
|
3398
|
-
);
|
|
3399
|
-
}
|
|
3400
|
-
}
|
|
3401
|
-
}
|
|
3402
|
-
function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
3403
|
-
for (const constraint of constraints) {
|
|
3404
|
-
if (constraint.path) {
|
|
3405
|
-
const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
|
|
3406
|
-
const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
|
|
3407
|
-
if (resolution.kind === "missing-property") {
|
|
3408
|
-
addUnknownPathTarget(
|
|
3409
|
-
ctx,
|
|
3410
|
-
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
|
|
3411
|
-
constraint.provenance
|
|
3412
|
-
);
|
|
3413
|
-
continue;
|
|
3414
|
-
}
|
|
3415
|
-
if (resolution.kind === "unresolvable") {
|
|
3416
|
-
addTypeMismatch(
|
|
3417
|
-
ctx,
|
|
3418
|
-
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(resolution.type)}" cannot be traversed`,
|
|
3419
|
-
constraint.provenance
|
|
3420
|
-
);
|
|
3421
|
-
continue;
|
|
3422
|
-
}
|
|
3423
|
-
checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
|
|
3424
|
-
continue;
|
|
3425
|
-
}
|
|
3426
|
-
checkConstraintOnType(ctx, fieldName, type, constraint);
|
|
3427
|
-
}
|
|
3428
|
-
}
|
|
3429
|
-
function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
3430
|
-
if (ctx.extensionRegistry === void 0) return;
|
|
3431
|
-
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
3432
|
-
if (registration === void 0) {
|
|
3433
|
-
addUnknownExtension(
|
|
3434
|
-
ctx,
|
|
3435
|
-
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not registered in the extension registry`,
|
|
3436
|
-
constraint.provenance
|
|
3437
|
-
);
|
|
3438
|
-
return;
|
|
3439
|
-
}
|
|
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
|
-
);
|
|
3062
|
+
var import_analysis2 = require("@formspec/analysis");
|
|
3063
|
+
function validateFieldNode(ctx, field) {
|
|
3064
|
+
const analysis = (0, import_analysis2.analyzeConstraintTargets)(
|
|
3065
|
+
field.name,
|
|
3066
|
+
field.type,
|
|
3067
|
+
field.constraints,
|
|
3068
|
+
ctx.typeRegistry,
|
|
3069
|
+
ctx.extensionRegistry === void 0 ? void 0 : {
|
|
3070
|
+
extensionRegistry: ctx.extensionRegistry
|
|
3463
3071
|
}
|
|
3464
|
-
return;
|
|
3465
|
-
}
|
|
3466
|
-
const applicableTypes = registration.applicableTypes;
|
|
3467
|
-
const matchesApplicableType = candidateTypes.some(
|
|
3468
|
-
(candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
|
|
3469
3072
|
);
|
|
3470
|
-
|
|
3471
|
-
addTypeMismatch(
|
|
3472
|
-
ctx,
|
|
3473
|
-
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3474
|
-
constraint.provenance
|
|
3475
|
-
);
|
|
3476
|
-
}
|
|
3477
|
-
}
|
|
3478
|
-
function validateFieldNode(ctx, field) {
|
|
3479
|
-
validateConstraints(ctx, field.name, field.type, [
|
|
3480
|
-
...collectReferencedTypeConstraints(ctx, field.type),
|
|
3481
|
-
...field.constraints
|
|
3482
|
-
]);
|
|
3073
|
+
ctx.diagnostics.push(...analysis.diagnostics);
|
|
3483
3074
|
if (field.type.kind === "object") {
|
|
3484
|
-
for (const
|
|
3485
|
-
validateObjectProperty(ctx, field.name,
|
|
3075
|
+
for (const property of field.type.properties) {
|
|
3076
|
+
validateObjectProperty(ctx, field.name, property);
|
|
3486
3077
|
}
|
|
3487
3078
|
}
|
|
3488
3079
|
}
|
|
3489
|
-
function validateObjectProperty(ctx, parentName,
|
|
3490
|
-
const qualifiedName = `${parentName}.${
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3080
|
+
function validateObjectProperty(ctx, parentName, property) {
|
|
3081
|
+
const qualifiedName = `${parentName}.${property.name}`;
|
|
3082
|
+
const analysis = (0, import_analysis2.analyzeConstraintTargets)(
|
|
3083
|
+
qualifiedName,
|
|
3084
|
+
property.type,
|
|
3085
|
+
property.constraints,
|
|
3086
|
+
ctx.typeRegistry,
|
|
3087
|
+
ctx.extensionRegistry === void 0 ? void 0 : {
|
|
3088
|
+
extensionRegistry: ctx.extensionRegistry
|
|
3089
|
+
}
|
|
3090
|
+
);
|
|
3091
|
+
ctx.diagnostics.push(...analysis.diagnostics);
|
|
3092
|
+
if (property.type.kind === "object") {
|
|
3093
|
+
for (const nestedProperty of property.type.properties) {
|
|
3094
|
+
validateObjectProperty(ctx, qualifiedName, nestedProperty);
|
|
3498
3095
|
}
|
|
3499
3096
|
}
|
|
3500
3097
|
}
|
|
3501
|
-
function validateConstraints(ctx, name, type, constraints) {
|
|
3502
|
-
checkNumericContradictions(ctx, name, constraints);
|
|
3503
|
-
checkLengthContradictions(ctx, name, constraints);
|
|
3504
|
-
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
3505
|
-
checkConstContradictions(ctx, name, constraints);
|
|
3506
|
-
checkConstraintBroadening(ctx, name, constraints);
|
|
3507
|
-
checkCustomConstraintSemantics(ctx, name, constraints);
|
|
3508
|
-
checkTypeApplicability(ctx, name, type, constraints);
|
|
3509
|
-
}
|
|
3510
3098
|
function validateElement(ctx, element) {
|
|
3511
3099
|
switch (element.kind) {
|
|
3512
3100
|
case "field":
|
|
@@ -3523,8 +3111,8 @@ function validateElement(ctx, element) {
|
|
|
3523
3111
|
}
|
|
3524
3112
|
break;
|
|
3525
3113
|
default: {
|
|
3526
|
-
const
|
|
3527
|
-
throw new Error(`Unhandled element kind: ${
|
|
3114
|
+
const exhaustive = element;
|
|
3115
|
+
throw new Error(`Unhandled element kind: ${String(exhaustive)}`);
|
|
3528
3116
|
}
|
|
3529
3117
|
}
|
|
3530
3118
|
}
|
|
@@ -3539,12 +3127,18 @@ function validateIR(ir, options) {
|
|
|
3539
3127
|
}
|
|
3540
3128
|
return {
|
|
3541
3129
|
diagnostics: ctx.diagnostics,
|
|
3542
|
-
valid: ctx.diagnostics.every((
|
|
3130
|
+
valid: ctx.diagnostics.every((diagnostic) => diagnostic.severity !== "error")
|
|
3543
3131
|
};
|
|
3544
3132
|
}
|
|
3545
3133
|
|
|
3546
3134
|
// src/generators/class-schema.ts
|
|
3547
3135
|
function generateClassSchemas(analysis, source, options) {
|
|
3136
|
+
const errorDiagnostics = analysis.diagnostics?.filter(
|
|
3137
|
+
(diagnostic) => diagnostic.severity === "error"
|
|
3138
|
+
);
|
|
3139
|
+
if (errorDiagnostics !== void 0 && errorDiagnostics.length > 0) {
|
|
3140
|
+
throw new Error(formatValidationError(errorDiagnostics));
|
|
3141
|
+
}
|
|
3548
3142
|
const ir = canonicalizeTSDoc(analysis, source);
|
|
3549
3143
|
const validationResult = validateIR(ir, {
|
|
3550
3144
|
...options?.extensionRegistry !== void 0 && {
|
|
@@ -3654,7 +3248,7 @@ function createExtensionRegistry(extensions) {
|
|
|
3654
3248
|
}
|
|
3655
3249
|
|
|
3656
3250
|
// src/generators/method-schema.ts
|
|
3657
|
-
var
|
|
3251
|
+
var import_core4 = require("@formspec/core");
|
|
3658
3252
|
function typeToJsonSchema(type, checker) {
|
|
3659
3253
|
const typeRegistry = {};
|
|
3660
3254
|
const visiting = /* @__PURE__ */ new Set();
|
|
@@ -3662,7 +3256,7 @@ function typeToJsonSchema(type, checker) {
|
|
|
3662
3256
|
const fieldProvenance = { surface: "tsdoc", file: "", line: 0, column: 0 };
|
|
3663
3257
|
const ir = {
|
|
3664
3258
|
kind: "form-ir",
|
|
3665
|
-
irVersion:
|
|
3259
|
+
irVersion: import_core4.IR_VERSION,
|
|
3666
3260
|
elements: [
|
|
3667
3261
|
{
|
|
3668
3262
|
kind: "field",
|