@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.js
CHANGED
|
@@ -398,6 +398,19 @@ import * as ts2 from "typescript";
|
|
|
398
398
|
|
|
399
399
|
// src/analyzer/tsdoc-parser.ts
|
|
400
400
|
import * as ts from "typescript";
|
|
401
|
+
import {
|
|
402
|
+
checkSyntheticTagApplication,
|
|
403
|
+
extractPathTarget as extractSharedPathTarget,
|
|
404
|
+
getTagDefinition,
|
|
405
|
+
hasTypeSemanticCapability,
|
|
406
|
+
parseConstraintTagValue,
|
|
407
|
+
parseDefaultValueTagValue,
|
|
408
|
+
resolveDeclarationPlacement,
|
|
409
|
+
resolvePathTargetType,
|
|
410
|
+
sliceCommentSpan,
|
|
411
|
+
parseCommentBlock,
|
|
412
|
+
parseTagSyntax
|
|
413
|
+
} from "@formspec/analysis";
|
|
401
414
|
import {
|
|
402
415
|
TSDocParser,
|
|
403
416
|
TSDocConfiguration,
|
|
@@ -412,30 +425,6 @@ import {
|
|
|
412
425
|
normalizeConstraintTagName,
|
|
413
426
|
isBuiltinConstraintName
|
|
414
427
|
} from "@formspec/core";
|
|
415
|
-
|
|
416
|
-
// src/analyzer/json-utils.ts
|
|
417
|
-
function tryParseJson(text) {
|
|
418
|
-
try {
|
|
419
|
-
return JSON.parse(text);
|
|
420
|
-
} catch {
|
|
421
|
-
return null;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// src/analyzer/tsdoc-parser.ts
|
|
426
|
-
var NUMERIC_CONSTRAINT_MAP = {
|
|
427
|
-
minimum: "minimum",
|
|
428
|
-
maximum: "maximum",
|
|
429
|
-
exclusiveMinimum: "exclusiveMinimum",
|
|
430
|
-
exclusiveMaximum: "exclusiveMaximum",
|
|
431
|
-
multipleOf: "multipleOf"
|
|
432
|
-
};
|
|
433
|
-
var LENGTH_CONSTRAINT_MAP = {
|
|
434
|
-
minLength: "minLength",
|
|
435
|
-
maxLength: "maxLength",
|
|
436
|
-
minItems: "minItems",
|
|
437
|
-
maxItems: "maxItems"
|
|
438
|
-
};
|
|
439
428
|
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
|
|
440
429
|
function createFormSpecTSDocConfig(extensionTagNames = []) {
|
|
441
430
|
const config = new TSDocConfiguration();
|
|
@@ -468,7 +457,294 @@ function createFormSpecTSDocConfig(extensionTagNames = []) {
|
|
|
468
457
|
}
|
|
469
458
|
return config;
|
|
470
459
|
}
|
|
460
|
+
function sharedCommentSyntaxOptions(options, offset) {
|
|
461
|
+
const extensions = options?.extensionRegistry?.extensions;
|
|
462
|
+
return {
|
|
463
|
+
...offset !== void 0 ? { offset } : {},
|
|
464
|
+
...extensions !== void 0 ? { extensions } : {}
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
function sharedTagValueOptions(options) {
|
|
468
|
+
return {
|
|
469
|
+
...options?.extensionRegistry !== void 0 ? { registry: options.extensionRegistry } : {},
|
|
470
|
+
...options?.fieldType !== void 0 ? { fieldType: options.fieldType } : {}
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
var SYNTHETIC_TYPE_FORMAT_FLAGS = ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope;
|
|
474
|
+
function buildSupportingDeclarations(sourceFile) {
|
|
475
|
+
return sourceFile.statements.filter(
|
|
476
|
+
(statement) => !ts.isImportDeclaration(statement) && !ts.isImportEqualsDeclaration(statement) && !(ts.isExportDeclaration(statement) && statement.moduleSpecifier !== void 0)
|
|
477
|
+
).map((statement) => statement.getText(sourceFile));
|
|
478
|
+
}
|
|
479
|
+
function renderSyntheticArgumentExpression(valueKind, argumentText) {
|
|
480
|
+
const trimmed = argumentText.trim();
|
|
481
|
+
if (trimmed === "") {
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
switch (valueKind) {
|
|
485
|
+
case "number":
|
|
486
|
+
case "integer":
|
|
487
|
+
case "signedInteger":
|
|
488
|
+
return Number.isFinite(Number(trimmed)) ? trimmed : JSON.stringify(trimmed);
|
|
489
|
+
case "string":
|
|
490
|
+
return JSON.stringify(argumentText);
|
|
491
|
+
case "json":
|
|
492
|
+
try {
|
|
493
|
+
JSON.parse(trimmed);
|
|
494
|
+
return `(${trimmed})`;
|
|
495
|
+
} catch {
|
|
496
|
+
return JSON.stringify(trimmed);
|
|
497
|
+
}
|
|
498
|
+
case "boolean":
|
|
499
|
+
return trimmed === "true" || trimmed === "false" ? trimmed : JSON.stringify(trimmed);
|
|
500
|
+
case "condition":
|
|
501
|
+
return "undefined as unknown as FormSpecCondition";
|
|
502
|
+
case null:
|
|
503
|
+
return null;
|
|
504
|
+
default: {
|
|
505
|
+
return String(valueKind);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
function getArrayElementType(type, checker) {
|
|
510
|
+
if (!checker.isArrayType(type)) {
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
return checker.getTypeArguments(type)[0] ?? null;
|
|
514
|
+
}
|
|
515
|
+
function supportsConstraintCapability(type, checker, capability) {
|
|
516
|
+
if (capability === void 0) {
|
|
517
|
+
return true;
|
|
518
|
+
}
|
|
519
|
+
if (hasTypeSemanticCapability(type, checker, capability)) {
|
|
520
|
+
return true;
|
|
521
|
+
}
|
|
522
|
+
if (capability === "string-like") {
|
|
523
|
+
const itemType = getArrayElementType(type, checker);
|
|
524
|
+
return itemType !== null && hasTypeSemanticCapability(itemType, checker, capability);
|
|
525
|
+
}
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
function makeDiagnostic(code, message, provenance) {
|
|
529
|
+
return {
|
|
530
|
+
code,
|
|
531
|
+
message,
|
|
532
|
+
severity: "error",
|
|
533
|
+
primaryLocation: provenance,
|
|
534
|
+
relatedLocations: []
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
function placementLabel(placement) {
|
|
538
|
+
switch (placement) {
|
|
539
|
+
case "class":
|
|
540
|
+
return "class declarations";
|
|
541
|
+
case "class-field":
|
|
542
|
+
return "class fields";
|
|
543
|
+
case "class-method":
|
|
544
|
+
return "class methods";
|
|
545
|
+
case "interface":
|
|
546
|
+
return "interface declarations";
|
|
547
|
+
case "interface-field":
|
|
548
|
+
return "interface fields";
|
|
549
|
+
case "type-alias":
|
|
550
|
+
return "type aliases";
|
|
551
|
+
case "type-alias-field":
|
|
552
|
+
return "type-alias properties";
|
|
553
|
+
case "variable":
|
|
554
|
+
return "variables";
|
|
555
|
+
case "function":
|
|
556
|
+
return "functions";
|
|
557
|
+
case "function-parameter":
|
|
558
|
+
return "function parameters";
|
|
559
|
+
case "method-parameter":
|
|
560
|
+
return "method parameters";
|
|
561
|
+
default: {
|
|
562
|
+
const exhaustive = placement;
|
|
563
|
+
return String(exhaustive);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function capabilityLabel(capability) {
|
|
568
|
+
switch (capability) {
|
|
569
|
+
case "numeric-comparable":
|
|
570
|
+
return "number";
|
|
571
|
+
case "string-like":
|
|
572
|
+
return "string";
|
|
573
|
+
case "array-like":
|
|
574
|
+
return "array";
|
|
575
|
+
case "enum-member-addressable":
|
|
576
|
+
return "enum";
|
|
577
|
+
case "json-like":
|
|
578
|
+
return "JSON-compatible";
|
|
579
|
+
case "object-like":
|
|
580
|
+
return "object";
|
|
581
|
+
case "condition-like":
|
|
582
|
+
return "conditional";
|
|
583
|
+
case void 0:
|
|
584
|
+
return "compatible";
|
|
585
|
+
default:
|
|
586
|
+
return capability;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
function getBroadenedCustomTypeId(fieldType) {
|
|
590
|
+
if (fieldType?.kind === "custom") {
|
|
591
|
+
return fieldType.typeId;
|
|
592
|
+
}
|
|
593
|
+
if (fieldType?.kind !== "union") {
|
|
594
|
+
return void 0;
|
|
595
|
+
}
|
|
596
|
+
const customMembers = fieldType.members.filter(
|
|
597
|
+
(member) => member.kind === "custom"
|
|
598
|
+
);
|
|
599
|
+
if (customMembers.length !== 1) {
|
|
600
|
+
return void 0;
|
|
601
|
+
}
|
|
602
|
+
const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
|
|
603
|
+
const allOtherMembersAreNull = nonCustomMembers.every(
|
|
604
|
+
(member) => member.kind === "primitive" && member.primitiveKind === "null"
|
|
605
|
+
);
|
|
606
|
+
const customMember = customMembers[0];
|
|
607
|
+
return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
|
|
608
|
+
}
|
|
609
|
+
function hasBuiltinConstraintBroadening(tagName, options) {
|
|
610
|
+
const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
|
|
611
|
+
return broadenedTypeId !== void 0 && options?.extensionRegistry?.findBuiltinConstraintBroadening(broadenedTypeId, tagName) !== void 0;
|
|
612
|
+
}
|
|
613
|
+
function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, parsedTag, provenance, supportingDeclarations, options) {
|
|
614
|
+
if (!isBuiltinConstraintName(tagName)) {
|
|
615
|
+
return [];
|
|
616
|
+
}
|
|
617
|
+
const checker = options?.checker;
|
|
618
|
+
const subjectType = options?.subjectType;
|
|
619
|
+
if (checker === void 0 || subjectType === void 0) {
|
|
620
|
+
return [];
|
|
621
|
+
}
|
|
622
|
+
const placement = resolveDeclarationPlacement(node);
|
|
623
|
+
if (placement === null) {
|
|
624
|
+
return [];
|
|
625
|
+
}
|
|
626
|
+
const definition = getTagDefinition(tagName, options?.extensionRegistry?.extensions);
|
|
627
|
+
if (definition === null) {
|
|
628
|
+
return [];
|
|
629
|
+
}
|
|
630
|
+
if (!definition.placements.includes(placement)) {
|
|
631
|
+
return [
|
|
632
|
+
makeDiagnostic(
|
|
633
|
+
"INVALID_TAG_PLACEMENT",
|
|
634
|
+
`Tag "@${tagName}" is not allowed on ${placementLabel(placement)}.`,
|
|
635
|
+
provenance
|
|
636
|
+
)
|
|
637
|
+
];
|
|
638
|
+
}
|
|
639
|
+
const target = parsedTag?.target ?? null;
|
|
640
|
+
const hasBroadening = target === null && hasBuiltinConstraintBroadening(tagName, options);
|
|
641
|
+
if (target !== null) {
|
|
642
|
+
if (target.kind !== "path") {
|
|
643
|
+
return [
|
|
644
|
+
makeDiagnostic(
|
|
645
|
+
"UNSUPPORTED_TARGETING_SYNTAX",
|
|
646
|
+
`Tag "@${tagName}" does not support ${target.kind} targeting syntax.`,
|
|
647
|
+
provenance
|
|
648
|
+
)
|
|
649
|
+
];
|
|
650
|
+
}
|
|
651
|
+
if (!target.valid || target.path === null) {
|
|
652
|
+
return [
|
|
653
|
+
makeDiagnostic(
|
|
654
|
+
"UNSUPPORTED_TARGETING_SYNTAX",
|
|
655
|
+
`Tag "@${tagName}" has invalid path targeting syntax.`,
|
|
656
|
+
provenance
|
|
657
|
+
)
|
|
658
|
+
];
|
|
659
|
+
}
|
|
660
|
+
const resolution = resolvePathTargetType(subjectType, checker, target.path.segments);
|
|
661
|
+
if (resolution.kind === "missing-property") {
|
|
662
|
+
return [
|
|
663
|
+
makeDiagnostic(
|
|
664
|
+
"UNKNOWN_PATH_TARGET",
|
|
665
|
+
`Target "${target.rawText}": path-targeted constraint "${tagName}" references unknown path segment "${resolution.segment}"`,
|
|
666
|
+
provenance
|
|
667
|
+
)
|
|
668
|
+
];
|
|
669
|
+
}
|
|
670
|
+
if (resolution.kind === "unresolvable") {
|
|
671
|
+
const actualType = checker.typeToString(resolution.type, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
672
|
+
return [
|
|
673
|
+
makeDiagnostic(
|
|
674
|
+
"TYPE_MISMATCH",
|
|
675
|
+
`Target "${target.rawText}": path-targeted constraint "${tagName}" is invalid because type "${actualType}" cannot be traversed`,
|
|
676
|
+
provenance
|
|
677
|
+
)
|
|
678
|
+
];
|
|
679
|
+
}
|
|
680
|
+
const requiredCapability = definition.capabilities[0];
|
|
681
|
+
if (requiredCapability !== void 0 && !supportsConstraintCapability(resolution.type, checker, requiredCapability)) {
|
|
682
|
+
const actualType = checker.typeToString(resolution.type, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
683
|
+
return [
|
|
684
|
+
makeDiagnostic(
|
|
685
|
+
"TYPE_MISMATCH",
|
|
686
|
+
`Target "${target.rawText}": constraint "${tagName}" is only valid on ${capabilityLabel(requiredCapability)} targets, but field type is "${actualType}"`,
|
|
687
|
+
provenance
|
|
688
|
+
)
|
|
689
|
+
];
|
|
690
|
+
}
|
|
691
|
+
} else if (!hasBroadening) {
|
|
692
|
+
const requiredCapability = definition.capabilities[0];
|
|
693
|
+
if (requiredCapability !== void 0 && !supportsConstraintCapability(subjectType, checker, requiredCapability)) {
|
|
694
|
+
const actualType = checker.typeToString(subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
695
|
+
return [
|
|
696
|
+
makeDiagnostic(
|
|
697
|
+
"TYPE_MISMATCH",
|
|
698
|
+
`Target "${node.getText(sourceFile)}": constraint "${tagName}" is only valid on ${capabilityLabel(requiredCapability)} targets, but field type is "${actualType}"`,
|
|
699
|
+
provenance
|
|
700
|
+
)
|
|
701
|
+
];
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
const argumentExpression = renderSyntheticArgumentExpression(
|
|
705
|
+
definition.valueKind,
|
|
706
|
+
parsedTag?.argumentText ?? ""
|
|
707
|
+
);
|
|
708
|
+
if (definition.requiresArgument && argumentExpression === null) {
|
|
709
|
+
return [];
|
|
710
|
+
}
|
|
711
|
+
if (hasBroadening) {
|
|
712
|
+
return [];
|
|
713
|
+
}
|
|
714
|
+
const subjectTypeText = checker.typeToString(subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
715
|
+
const hostType = options?.hostType ?? subjectType;
|
|
716
|
+
const hostTypeText = checker.typeToString(hostType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
717
|
+
const result = checkSyntheticTagApplication({
|
|
718
|
+
tagName,
|
|
719
|
+
placement,
|
|
720
|
+
hostType: hostTypeText,
|
|
721
|
+
subjectType: subjectTypeText,
|
|
722
|
+
...target?.kind === "path" ? { target: { kind: "path", text: target.rawText } } : {},
|
|
723
|
+
...argumentExpression !== null ? { argumentExpression } : {},
|
|
724
|
+
supportingDeclarations,
|
|
725
|
+
...options?.extensionRegistry !== void 0 ? {
|
|
726
|
+
extensions: options.extensionRegistry.extensions.map((extension) => ({
|
|
727
|
+
extensionId: extension.extensionId,
|
|
728
|
+
...extension.constraintTags !== void 0 ? {
|
|
729
|
+
constraintTags: extension.constraintTags.map((tag) => ({ tagName: tag.tagName }))
|
|
730
|
+
} : {}
|
|
731
|
+
}))
|
|
732
|
+
} : {}
|
|
733
|
+
});
|
|
734
|
+
if (result.diagnostics.length === 0) {
|
|
735
|
+
return [];
|
|
736
|
+
}
|
|
737
|
+
const expectedLabel = definition.valueKind === null ? "compatible argument" : capabilityLabel(definition.valueKind);
|
|
738
|
+
return [
|
|
739
|
+
makeDiagnostic(
|
|
740
|
+
"TYPE_MISMATCH",
|
|
741
|
+
`Tag "@${tagName}" received an invalid argument for ${expectedLabel}.`,
|
|
742
|
+
provenance
|
|
743
|
+
)
|
|
744
|
+
];
|
|
745
|
+
}
|
|
471
746
|
var parserCache = /* @__PURE__ */ new Map();
|
|
747
|
+
var parseResultCache = /* @__PURE__ */ new Map();
|
|
472
748
|
function getParser(options) {
|
|
473
749
|
const extensionTagNames = [
|
|
474
750
|
...options?.extensionRegistry?.extensions.flatMap(
|
|
@@ -484,18 +760,54 @@ function getParser(options) {
|
|
|
484
760
|
parserCache.set(cacheKey, parser);
|
|
485
761
|
return parser;
|
|
486
762
|
}
|
|
763
|
+
function getExtensionRegistryCacheKey(registry) {
|
|
764
|
+
if (registry === void 0) {
|
|
765
|
+
return "";
|
|
766
|
+
}
|
|
767
|
+
return registry.extensions.map(
|
|
768
|
+
(extension) => JSON.stringify({
|
|
769
|
+
extensionId: extension.extensionId,
|
|
770
|
+
typeNames: extension.types?.map((type) => type.typeName) ?? [],
|
|
771
|
+
constraintTags: extension.constraintTags?.map((tag) => tag.tagName) ?? []
|
|
772
|
+
})
|
|
773
|
+
).join("|");
|
|
774
|
+
}
|
|
775
|
+
function getParseCacheKey(node, file, options) {
|
|
776
|
+
const sourceFile = node.getSourceFile();
|
|
777
|
+
const checker = options?.checker;
|
|
778
|
+
return JSON.stringify({
|
|
779
|
+
file,
|
|
780
|
+
sourceFile: sourceFile.fileName,
|
|
781
|
+
sourceText: sourceFile.text,
|
|
782
|
+
start: node.getFullStart(),
|
|
783
|
+
end: node.getEnd(),
|
|
784
|
+
fieldType: options?.fieldType ?? null,
|
|
785
|
+
subjectType: checker !== void 0 && options?.subjectType !== void 0 ? checker.typeToString(options.subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS) : null,
|
|
786
|
+
hostType: checker !== void 0 && options?.hostType !== void 0 ? checker.typeToString(options.hostType, node, SYNTHETIC_TYPE_FORMAT_FLAGS) : null,
|
|
787
|
+
extensions: getExtensionRegistryCacheKey(options?.extensionRegistry)
|
|
788
|
+
});
|
|
789
|
+
}
|
|
487
790
|
function parseTSDocTags(node, file = "", options) {
|
|
791
|
+
const cacheKey = getParseCacheKey(node, file, options);
|
|
792
|
+
const cached = parseResultCache.get(cacheKey);
|
|
793
|
+
if (cached !== void 0) {
|
|
794
|
+
return cached;
|
|
795
|
+
}
|
|
488
796
|
const constraints = [];
|
|
489
797
|
const annotations = [];
|
|
798
|
+
const diagnostics = [];
|
|
490
799
|
let displayName;
|
|
491
800
|
let description;
|
|
492
801
|
let placeholder;
|
|
493
802
|
let displayNameProvenance;
|
|
494
803
|
let descriptionProvenance;
|
|
495
804
|
let placeholderProvenance;
|
|
805
|
+
const rawTextTags = [];
|
|
496
806
|
const sourceFile = node.getSourceFile();
|
|
497
807
|
const sourceText = sourceFile.getFullText();
|
|
808
|
+
const supportingDeclarations = buildSupportingDeclarations(sourceFile);
|
|
498
809
|
const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
810
|
+
const rawTextFallbacks = collectRawTextFallbacks(node, file);
|
|
499
811
|
if (commentRanges) {
|
|
500
812
|
for (const range of commentRanges) {
|
|
501
813
|
if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
|
|
@@ -510,12 +822,33 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
510
822
|
TextRange.fromStringRange(sourceText, range.pos, range.end)
|
|
511
823
|
);
|
|
512
824
|
const docComment = parserContext.docComment;
|
|
825
|
+
const parsedComment = parseCommentBlock(
|
|
826
|
+
commentText,
|
|
827
|
+
sharedCommentSyntaxOptions(options, range.pos)
|
|
828
|
+
);
|
|
829
|
+
let parsedTagCursor = 0;
|
|
830
|
+
const nextParsedTag = (normalizedTagName) => {
|
|
831
|
+
while (parsedTagCursor < parsedComment.tags.length) {
|
|
832
|
+
const candidate = parsedComment.tags[parsedTagCursor];
|
|
833
|
+
parsedTagCursor += 1;
|
|
834
|
+
if (candidate?.normalizedTagName === normalizedTagName) {
|
|
835
|
+
return candidate;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return null;
|
|
839
|
+
};
|
|
840
|
+
for (const parsedTag of parsedComment.tags) {
|
|
841
|
+
if (TAGS_REQUIRING_RAW_TEXT.has(parsedTag.normalizedTagName)) {
|
|
842
|
+
rawTextTags.push({ tag: parsedTag, commentText, commentOffset: range.pos });
|
|
843
|
+
}
|
|
844
|
+
}
|
|
513
845
|
for (const block of docComment.customBlocks) {
|
|
514
846
|
const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
|
|
847
|
+
const parsedTag = nextParsedTag(tagName);
|
|
515
848
|
if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
|
|
516
|
-
const text2 =
|
|
849
|
+
const text2 = getBestBlockPayloadText(parsedTag, commentText, range.pos, block);
|
|
517
850
|
if (text2 === "") continue;
|
|
518
|
-
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
851
|
+
const provenance2 = parsedTag !== null ? provenanceForParsedTag(parsedTag, sourceFile, file) : provenanceForComment(range, sourceFile, file, tagName);
|
|
519
852
|
switch (tagName) {
|
|
520
853
|
case "displayName":
|
|
521
854
|
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
@@ -545,11 +878,29 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
545
878
|
continue;
|
|
546
879
|
}
|
|
547
880
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
548
|
-
const text =
|
|
881
|
+
const text = getBestBlockPayloadText(parsedTag, commentText, range.pos, block);
|
|
549
882
|
const expectedType = isBuiltinConstraintName(tagName) ? BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
|
|
550
883
|
if (text === "" && expectedType !== "boolean") continue;
|
|
551
|
-
const provenance = provenanceForComment(range, sourceFile, file, tagName);
|
|
552
|
-
const
|
|
884
|
+
const provenance = parsedTag !== null ? provenanceForParsedTag(parsedTag, sourceFile, file) : provenanceForComment(range, sourceFile, file, tagName);
|
|
885
|
+
const compilerDiagnostics = buildCompilerBackedConstraintDiagnostics(
|
|
886
|
+
node,
|
|
887
|
+
sourceFile,
|
|
888
|
+
tagName,
|
|
889
|
+
parsedTag,
|
|
890
|
+
provenance,
|
|
891
|
+
supportingDeclarations,
|
|
892
|
+
options
|
|
893
|
+
);
|
|
894
|
+
if (compilerDiagnostics.length > 0) {
|
|
895
|
+
diagnostics.push(...compilerDiagnostics);
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
const constraintNode = parseConstraintTagValue(
|
|
899
|
+
tagName,
|
|
900
|
+
text,
|
|
901
|
+
provenance,
|
|
902
|
+
sharedTagValueOptions(options)
|
|
903
|
+
);
|
|
553
904
|
if (constraintNode) {
|
|
554
905
|
constraints.push(constraintNode);
|
|
555
906
|
}
|
|
@@ -603,57 +954,114 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
603
954
|
provenance: placeholderProvenance
|
|
604
955
|
});
|
|
605
956
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
const
|
|
616
|
-
|
|
617
|
-
|
|
957
|
+
if (rawTextTags.length > 0) {
|
|
958
|
+
for (const rawTextTag of rawTextTags) {
|
|
959
|
+
const fallbackQueue = rawTextFallbacks.get(rawTextTag.tag.normalizedTagName);
|
|
960
|
+
const fallback = fallbackQueue?.shift();
|
|
961
|
+
const text = choosePreferredPayloadText(
|
|
962
|
+
getSharedPayloadText(rawTextTag.tag, rawTextTag.commentText, rawTextTag.commentOffset),
|
|
963
|
+
fallback?.text ?? ""
|
|
964
|
+
);
|
|
965
|
+
if (text === "") continue;
|
|
966
|
+
const provenance = provenanceForParsedTag(rawTextTag.tag, sourceFile, file);
|
|
967
|
+
if (rawTextTag.tag.normalizedTagName === "defaultValue") {
|
|
968
|
+
const defaultValueNode = parseDefaultValueTagValue(text, provenance);
|
|
969
|
+
annotations.push(defaultValueNode);
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
972
|
+
const compilerDiagnostics = buildCompilerBackedConstraintDiagnostics(
|
|
973
|
+
node,
|
|
974
|
+
sourceFile,
|
|
975
|
+
rawTextTag.tag.normalizedTagName,
|
|
976
|
+
rawTextTag.tag,
|
|
977
|
+
provenance,
|
|
978
|
+
supportingDeclarations,
|
|
979
|
+
options
|
|
980
|
+
);
|
|
981
|
+
if (compilerDiagnostics.length > 0) {
|
|
982
|
+
diagnostics.push(...compilerDiagnostics);
|
|
983
|
+
continue;
|
|
984
|
+
}
|
|
985
|
+
const constraintNode = parseConstraintTagValue(
|
|
986
|
+
rawTextTag.tag.normalizedTagName,
|
|
987
|
+
text,
|
|
988
|
+
provenance,
|
|
989
|
+
sharedTagValueOptions(options)
|
|
990
|
+
);
|
|
991
|
+
if (constraintNode) {
|
|
992
|
+
constraints.push(constraintNode);
|
|
993
|
+
}
|
|
618
994
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
995
|
+
}
|
|
996
|
+
for (const [tagName, fallbacks] of rawTextFallbacks) {
|
|
997
|
+
for (const fallback of fallbacks) {
|
|
998
|
+
const text = fallback.text.trim();
|
|
999
|
+
if (text === "") continue;
|
|
1000
|
+
const provenance = fallback.provenance;
|
|
1001
|
+
if (tagName === "defaultValue") {
|
|
1002
|
+
const defaultValueNode = parseDefaultValueTagValue(text, provenance);
|
|
1003
|
+
annotations.push(defaultValueNode);
|
|
1004
|
+
continue;
|
|
1005
|
+
}
|
|
1006
|
+
const compilerDiagnostics = buildCompilerBackedConstraintDiagnostics(
|
|
1007
|
+
node,
|
|
1008
|
+
sourceFile,
|
|
1009
|
+
tagName,
|
|
1010
|
+
null,
|
|
1011
|
+
provenance,
|
|
1012
|
+
supportingDeclarations,
|
|
1013
|
+
options
|
|
1014
|
+
);
|
|
1015
|
+
if (compilerDiagnostics.length > 0) {
|
|
1016
|
+
diagnostics.push(...compilerDiagnostics);
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
const constraintNode = parseConstraintTagValue(
|
|
1020
|
+
tagName,
|
|
1021
|
+
text,
|
|
1022
|
+
provenance,
|
|
1023
|
+
sharedTagValueOptions(options)
|
|
1024
|
+
);
|
|
1025
|
+
if (constraintNode) {
|
|
1026
|
+
constraints.push(constraintNode);
|
|
1027
|
+
}
|
|
622
1028
|
}
|
|
623
1029
|
}
|
|
624
|
-
|
|
1030
|
+
const result = { constraints, annotations, diagnostics };
|
|
1031
|
+
parseResultCache.set(cacheKey, result);
|
|
1032
|
+
return result;
|
|
625
1033
|
}
|
|
626
1034
|
function extractDisplayNameMetadata(node) {
|
|
627
1035
|
let displayName;
|
|
628
1036
|
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
1037
|
+
const sourceFile = node.getSourceFile();
|
|
1038
|
+
const sourceText = sourceFile.getFullText();
|
|
1039
|
+
const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
1040
|
+
if (commentRanges) {
|
|
1041
|
+
for (const range of commentRanges) {
|
|
1042
|
+
if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) continue;
|
|
1043
|
+
const commentText = sourceText.substring(range.pos, range.end);
|
|
1044
|
+
if (!commentText.startsWith("/**")) continue;
|
|
1045
|
+
const parsed = parseCommentBlock(commentText);
|
|
1046
|
+
for (const tag of parsed.tags) {
|
|
1047
|
+
if (tag.normalizedTagName !== "displayName") {
|
|
1048
|
+
continue;
|
|
1049
|
+
}
|
|
1050
|
+
if (tag.target !== null && tag.argumentText !== "") {
|
|
1051
|
+
memberDisplayNames.set(tag.target.rawText, tag.argumentText);
|
|
1052
|
+
continue;
|
|
1053
|
+
}
|
|
1054
|
+
if (tag.argumentText !== "") {
|
|
1055
|
+
displayName ??= tag.argumentText;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
642
1059
|
}
|
|
643
1060
|
return {
|
|
644
1061
|
...displayName !== void 0 && { displayName },
|
|
645
1062
|
memberDisplayNames
|
|
646
1063
|
};
|
|
647
1064
|
}
|
|
648
|
-
function extractPathTarget(text) {
|
|
649
|
-
const trimmed = text.trimStart();
|
|
650
|
-
const match = /^:([a-zA-Z_]\w*)(?:\s+([\s\S]*))?$/.exec(trimmed);
|
|
651
|
-
if (!match?.[1]) return null;
|
|
652
|
-
return {
|
|
653
|
-
path: { segments: [match[1]] },
|
|
654
|
-
remainingText: match[2] ?? ""
|
|
655
|
-
};
|
|
656
|
-
}
|
|
657
1065
|
function extractBlockText(block) {
|
|
658
1066
|
return extractPlainText(block.content);
|
|
659
1067
|
}
|
|
@@ -672,219 +1080,48 @@ function extractPlainText(node) {
|
|
|
672
1080
|
}
|
|
673
1081
|
return result;
|
|
674
1082
|
}
|
|
675
|
-
function
|
|
676
|
-
const
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
if (
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
const pathResult = extractPathTarget(text);
|
|
684
|
-
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
685
|
-
const path2 = pathResult?.path;
|
|
686
|
-
const expectedType = BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
|
|
687
|
-
if (expectedType === "number") {
|
|
688
|
-
const value = Number(effectiveText);
|
|
689
|
-
if (Number.isNaN(value)) {
|
|
690
|
-
return null;
|
|
691
|
-
}
|
|
692
|
-
const numericKind = NUMERIC_CONSTRAINT_MAP[tagName];
|
|
693
|
-
if (numericKind) {
|
|
694
|
-
return {
|
|
695
|
-
kind: "constraint",
|
|
696
|
-
constraintKind: numericKind,
|
|
697
|
-
value,
|
|
698
|
-
...path2 && { path: path2 },
|
|
699
|
-
provenance
|
|
700
|
-
};
|
|
701
|
-
}
|
|
702
|
-
const lengthKind = LENGTH_CONSTRAINT_MAP[tagName];
|
|
703
|
-
if (lengthKind) {
|
|
704
|
-
return {
|
|
705
|
-
kind: "constraint",
|
|
706
|
-
constraintKind: lengthKind,
|
|
707
|
-
value,
|
|
708
|
-
...path2 && { path: path2 },
|
|
709
|
-
provenance
|
|
710
|
-
};
|
|
711
|
-
}
|
|
712
|
-
return null;
|
|
713
|
-
}
|
|
714
|
-
if (expectedType === "boolean") {
|
|
715
|
-
const trimmed = effectiveText.trim();
|
|
716
|
-
if (trimmed !== "" && trimmed !== "true") {
|
|
717
|
-
return null;
|
|
718
|
-
}
|
|
719
|
-
if (tagName === "uniqueItems") {
|
|
720
|
-
return {
|
|
721
|
-
kind: "constraint",
|
|
722
|
-
constraintKind: "uniqueItems",
|
|
723
|
-
value: true,
|
|
724
|
-
...path2 && { path: path2 },
|
|
725
|
-
provenance
|
|
726
|
-
};
|
|
727
|
-
}
|
|
728
|
-
return null;
|
|
1083
|
+
function choosePreferredPayloadText(primary, fallback) {
|
|
1084
|
+
const preferred = primary.trim();
|
|
1085
|
+
const alternate = fallback.trim();
|
|
1086
|
+
if (preferred === "") return alternate;
|
|
1087
|
+
if (alternate === "") return preferred;
|
|
1088
|
+
if (alternate.includes("\n")) return alternate;
|
|
1089
|
+
if (alternate.length > preferred.length && alternate.startsWith(preferred)) {
|
|
1090
|
+
return alternate;
|
|
729
1091
|
}
|
|
730
|
-
|
|
731
|
-
if (tagName === "const") {
|
|
732
|
-
const trimmedText = effectiveText.trim();
|
|
733
|
-
if (trimmedText === "") return null;
|
|
734
|
-
try {
|
|
735
|
-
const parsed2 = JSON.parse(trimmedText);
|
|
736
|
-
return {
|
|
737
|
-
kind: "constraint",
|
|
738
|
-
constraintKind: "const",
|
|
739
|
-
value: parsed2,
|
|
740
|
-
...path2 && { path: path2 },
|
|
741
|
-
provenance
|
|
742
|
-
};
|
|
743
|
-
} catch {
|
|
744
|
-
return {
|
|
745
|
-
kind: "constraint",
|
|
746
|
-
constraintKind: "const",
|
|
747
|
-
value: trimmedText,
|
|
748
|
-
...path2 && { path: path2 },
|
|
749
|
-
provenance
|
|
750
|
-
};
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
const parsed = tryParseJson(effectiveText);
|
|
754
|
-
if (!Array.isArray(parsed)) {
|
|
755
|
-
return null;
|
|
756
|
-
}
|
|
757
|
-
const members = [];
|
|
758
|
-
for (const item of parsed) {
|
|
759
|
-
if (typeof item === "string" || typeof item === "number") {
|
|
760
|
-
members.push(item);
|
|
761
|
-
} else if (typeof item === "object" && item !== null && "id" in item) {
|
|
762
|
-
const id = item["id"];
|
|
763
|
-
if (typeof id === "string" || typeof id === "number") {
|
|
764
|
-
members.push(id);
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
return {
|
|
769
|
-
kind: "constraint",
|
|
770
|
-
constraintKind: "allowedMembers",
|
|
771
|
-
members,
|
|
772
|
-
...path2 && { path: path2 },
|
|
773
|
-
provenance
|
|
774
|
-
};
|
|
775
|
-
}
|
|
776
|
-
return {
|
|
777
|
-
kind: "constraint",
|
|
778
|
-
constraintKind: "pattern",
|
|
779
|
-
pattern: effectiveText,
|
|
780
|
-
...path2 && { path: path2 },
|
|
781
|
-
provenance
|
|
782
|
-
};
|
|
1092
|
+
return preferred;
|
|
783
1093
|
}
|
|
784
|
-
function
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
const path2 = pathResult?.path;
|
|
788
|
-
const registry = options?.extensionRegistry;
|
|
789
|
-
if (registry === void 0) {
|
|
790
|
-
return null;
|
|
791
|
-
}
|
|
792
|
-
const directTag = registry.findConstraintTag(tagName);
|
|
793
|
-
if (directTag !== void 0) {
|
|
794
|
-
return makeCustomConstraintNode(
|
|
795
|
-
directTag.extensionId,
|
|
796
|
-
directTag.registration.constraintName,
|
|
797
|
-
directTag.registration.parseValue(effectiveText),
|
|
798
|
-
provenance,
|
|
799
|
-
path2,
|
|
800
|
-
registry
|
|
801
|
-
);
|
|
1094
|
+
function getSharedPayloadText(tag, commentText, commentOffset) {
|
|
1095
|
+
if (tag.payloadSpan === null) {
|
|
1096
|
+
return "";
|
|
802
1097
|
}
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
}
|
|
806
|
-
const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
|
|
807
|
-
if (broadenedTypeId === void 0) {
|
|
808
|
-
return null;
|
|
809
|
-
}
|
|
810
|
-
const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
|
|
811
|
-
if (broadened === void 0) {
|
|
812
|
-
return null;
|
|
813
|
-
}
|
|
814
|
-
return makeCustomConstraintNode(
|
|
815
|
-
broadened.extensionId,
|
|
816
|
-
broadened.registration.constraintName,
|
|
817
|
-
broadened.registration.parseValue(effectiveText),
|
|
818
|
-
provenance,
|
|
819
|
-
path2,
|
|
820
|
-
registry
|
|
821
|
-
);
|
|
1098
|
+
return sliceCommentSpan(commentText, tag.payloadSpan, {
|
|
1099
|
+
offset: commentOffset
|
|
1100
|
+
}).trim();
|
|
822
1101
|
}
|
|
823
|
-
function
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
if (fieldType?.kind !== "union") {
|
|
828
|
-
return void 0;
|
|
829
|
-
}
|
|
830
|
-
const customMembers = fieldType.members.filter(
|
|
831
|
-
(member) => member.kind === "custom"
|
|
832
|
-
);
|
|
833
|
-
if (customMembers.length !== 1) {
|
|
834
|
-
return void 0;
|
|
835
|
-
}
|
|
836
|
-
const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
|
|
837
|
-
const allOtherMembersAreNull = nonCustomMembers.every(
|
|
838
|
-
(member) => member.kind === "primitive" && member.primitiveKind === "null"
|
|
839
|
-
);
|
|
840
|
-
const customMember = customMembers[0];
|
|
841
|
-
return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
|
|
1102
|
+
function getBestBlockPayloadText(tag, commentText, commentOffset, block) {
|
|
1103
|
+
const sharedText = tag === null ? "" : getSharedPayloadText(tag, commentText, commentOffset);
|
|
1104
|
+
const blockText = extractBlockText(block).replace(/\s+/g, " ").trim();
|
|
1105
|
+
return choosePreferredPayloadText(sharedText, blockText);
|
|
842
1106
|
}
|
|
843
|
-
function
|
|
844
|
-
const
|
|
845
|
-
const
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
);
|
|
1107
|
+
function collectRawTextFallbacks(node, file) {
|
|
1108
|
+
const fallbacks = /* @__PURE__ */ new Map();
|
|
1109
|
+
for (const tag of ts.getJSDocTags(node)) {
|
|
1110
|
+
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
1111
|
+
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1112
|
+
const commentText = getTagCommentText(tag)?.trim() ?? "";
|
|
1113
|
+
if (commentText === "") continue;
|
|
1114
|
+
const entries = fallbacks.get(tagName) ?? [];
|
|
1115
|
+
entries.push({
|
|
1116
|
+
text: commentText,
|
|
1117
|
+
provenance: provenanceForJSDocTag(tag, file)
|
|
1118
|
+
});
|
|
1119
|
+
fallbacks.set(tagName, entries);
|
|
850
1120
|
}
|
|
851
|
-
return
|
|
852
|
-
kind: "constraint",
|
|
853
|
-
constraintKind: "custom",
|
|
854
|
-
constraintId,
|
|
855
|
-
payload,
|
|
856
|
-
compositionRule: registration.compositionRule,
|
|
857
|
-
...path2 && { path: path2 },
|
|
858
|
-
provenance
|
|
859
|
-
};
|
|
860
|
-
}
|
|
861
|
-
function parseDefaultValueValue(text, provenance) {
|
|
862
|
-
const trimmed = text.trim();
|
|
863
|
-
let value;
|
|
864
|
-
if (trimmed === "null") {
|
|
865
|
-
value = null;
|
|
866
|
-
} else if (trimmed === "true") {
|
|
867
|
-
value = true;
|
|
868
|
-
} else if (trimmed === "false") {
|
|
869
|
-
value = false;
|
|
870
|
-
} else {
|
|
871
|
-
const parsed = tryParseJson(trimmed);
|
|
872
|
-
value = parsed !== null ? parsed : trimmed;
|
|
873
|
-
}
|
|
874
|
-
return {
|
|
875
|
-
kind: "annotation",
|
|
876
|
-
annotationKind: "defaultValue",
|
|
877
|
-
value,
|
|
878
|
-
provenance
|
|
879
|
-
};
|
|
1121
|
+
return fallbacks;
|
|
880
1122
|
}
|
|
881
1123
|
function isMemberTargetDisplayName(text) {
|
|
882
|
-
return
|
|
883
|
-
}
|
|
884
|
-
function parseMemberTargetDisplayName(text) {
|
|
885
|
-
const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
|
|
886
|
-
if (!match?.[1] || !match[2]) return null;
|
|
887
|
-
return { target: match[1], label: match[2].trim() };
|
|
1124
|
+
return parseTagSyntax("displayName", text).target !== null;
|
|
888
1125
|
}
|
|
889
1126
|
function provenanceForComment(range, sourceFile, file, tagName) {
|
|
890
1127
|
const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
|
|
@@ -896,6 +1133,16 @@ function provenanceForComment(range, sourceFile, file, tagName) {
|
|
|
896
1133
|
tagName: "@" + tagName
|
|
897
1134
|
};
|
|
898
1135
|
}
|
|
1136
|
+
function provenanceForParsedTag(tag, sourceFile, file) {
|
|
1137
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(tag.tagNameSpan.start);
|
|
1138
|
+
return {
|
|
1139
|
+
surface: "tsdoc",
|
|
1140
|
+
file,
|
|
1141
|
+
line: line + 1,
|
|
1142
|
+
column: character,
|
|
1143
|
+
tagName: "@" + tag.normalizedTagName
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
899
1146
|
function provenanceForJSDocTag(tag, file) {
|
|
900
1147
|
const sourceFile = tag.getSourceFile();
|
|
901
1148
|
const { line, character } = sourceFile.getLineAndCharacterOfPosition(tag.getStart());
|
|
@@ -918,12 +1165,15 @@ function getTagCommentText(tag) {
|
|
|
918
1165
|
}
|
|
919
1166
|
|
|
920
1167
|
// src/analyzer/jsdoc-constraints.ts
|
|
1168
|
+
function extractJSDocParseResult(node, file = "", options) {
|
|
1169
|
+
return parseTSDocTags(node, file, options);
|
|
1170
|
+
}
|
|
921
1171
|
function extractJSDocConstraintNodes(node, file = "", options) {
|
|
922
|
-
const result =
|
|
1172
|
+
const result = extractJSDocParseResult(node, file, options);
|
|
923
1173
|
return [...result.constraints];
|
|
924
1174
|
}
|
|
925
1175
|
function extractJSDocAnnotationNodes(node, file = "", options) {
|
|
926
|
-
const result =
|
|
1176
|
+
const result = extractJSDocParseResult(node, file, options);
|
|
927
1177
|
return [...result.annotations];
|
|
928
1178
|
}
|
|
929
1179
|
function extractDefaultValueAnnotation(initializer, file = "") {
|
|
@@ -972,13 +1222,16 @@ var RESOLVING_TYPE_PLACEHOLDER = {
|
|
|
972
1222
|
properties: [],
|
|
973
1223
|
additionalProperties: true
|
|
974
1224
|
};
|
|
975
|
-
function makeParseOptions(extensionRegistry, fieldType) {
|
|
976
|
-
if (extensionRegistry === void 0 && fieldType === void 0) {
|
|
1225
|
+
function makeParseOptions(extensionRegistry, fieldType, checker, subjectType, hostType) {
|
|
1226
|
+
if (extensionRegistry === void 0 && fieldType === void 0 && checker === void 0 && subjectType === void 0 && hostType === void 0) {
|
|
977
1227
|
return void 0;
|
|
978
1228
|
}
|
|
979
1229
|
return {
|
|
980
1230
|
...extensionRegistry !== void 0 && { extensionRegistry },
|
|
981
|
-
...fieldType !== void 0 && { fieldType }
|
|
1231
|
+
...fieldType !== void 0 && { fieldType },
|
|
1232
|
+
...checker !== void 0 && { checker },
|
|
1233
|
+
...subjectType !== void 0 && { subjectType },
|
|
1234
|
+
...hostType !== void 0 && { hostType }
|
|
982
1235
|
};
|
|
983
1236
|
}
|
|
984
1237
|
function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
@@ -986,11 +1239,15 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
986
1239
|
const fields = [];
|
|
987
1240
|
const fieldLayouts = [];
|
|
988
1241
|
const typeRegistry = {};
|
|
989
|
-
const
|
|
1242
|
+
const diagnostics = [];
|
|
1243
|
+
const classType = checker.getTypeAtLocation(classDecl);
|
|
1244
|
+
const classDoc = extractJSDocParseResult(
|
|
990
1245
|
classDecl,
|
|
991
1246
|
file,
|
|
992
|
-
makeParseOptions(extensionRegistry)
|
|
1247
|
+
makeParseOptions(extensionRegistry, void 0, checker, classType, classType)
|
|
993
1248
|
);
|
|
1249
|
+
const annotations = [...classDoc.annotations];
|
|
1250
|
+
diagnostics.push(...classDoc.diagnostics);
|
|
994
1251
|
const visiting = /* @__PURE__ */ new Set();
|
|
995
1252
|
const instanceMethods = [];
|
|
996
1253
|
const staticMethods = [];
|
|
@@ -1002,6 +1259,8 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1002
1259
|
file,
|
|
1003
1260
|
typeRegistry,
|
|
1004
1261
|
visiting,
|
|
1262
|
+
diagnostics,
|
|
1263
|
+
classType,
|
|
1005
1264
|
extensionRegistry
|
|
1006
1265
|
);
|
|
1007
1266
|
if (fieldNode) {
|
|
@@ -1026,6 +1285,7 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1026
1285
|
fieldLayouts,
|
|
1027
1286
|
typeRegistry,
|
|
1028
1287
|
...annotations.length > 0 && { annotations },
|
|
1288
|
+
...diagnostics.length > 0 && { diagnostics },
|
|
1029
1289
|
instanceMethods,
|
|
1030
1290
|
staticMethods
|
|
1031
1291
|
};
|
|
@@ -1034,11 +1294,15 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
1034
1294
|
const name = interfaceDecl.name.text;
|
|
1035
1295
|
const fields = [];
|
|
1036
1296
|
const typeRegistry = {};
|
|
1037
|
-
const
|
|
1297
|
+
const diagnostics = [];
|
|
1298
|
+
const interfaceType = checker.getTypeAtLocation(interfaceDecl);
|
|
1299
|
+
const interfaceDoc = extractJSDocParseResult(
|
|
1038
1300
|
interfaceDecl,
|
|
1039
1301
|
file,
|
|
1040
|
-
makeParseOptions(extensionRegistry)
|
|
1302
|
+
makeParseOptions(extensionRegistry, void 0, checker, interfaceType, interfaceType)
|
|
1041
1303
|
);
|
|
1304
|
+
const annotations = [...interfaceDoc.annotations];
|
|
1305
|
+
diagnostics.push(...interfaceDoc.diagnostics);
|
|
1042
1306
|
const visiting = /* @__PURE__ */ new Set();
|
|
1043
1307
|
for (const member of interfaceDecl.members) {
|
|
1044
1308
|
if (ts3.isPropertySignature(member)) {
|
|
@@ -1048,6 +1312,8 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
1048
1312
|
file,
|
|
1049
1313
|
typeRegistry,
|
|
1050
1314
|
visiting,
|
|
1315
|
+
diagnostics,
|
|
1316
|
+
interfaceType,
|
|
1051
1317
|
extensionRegistry
|
|
1052
1318
|
);
|
|
1053
1319
|
if (fieldNode) {
|
|
@@ -1062,6 +1328,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
1062
1328
|
fieldLayouts,
|
|
1063
1329
|
typeRegistry,
|
|
1064
1330
|
...annotations.length > 0 && { annotations },
|
|
1331
|
+
...diagnostics.length > 0 && { diagnostics },
|
|
1065
1332
|
instanceMethods: [],
|
|
1066
1333
|
staticMethods: []
|
|
1067
1334
|
};
|
|
@@ -1079,11 +1346,15 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
1079
1346
|
const name = typeAlias.name.text;
|
|
1080
1347
|
const fields = [];
|
|
1081
1348
|
const typeRegistry = {};
|
|
1082
|
-
const
|
|
1349
|
+
const diagnostics = [];
|
|
1350
|
+
const aliasType = checker.getTypeAtLocation(typeAlias);
|
|
1351
|
+
const typeAliasDoc = extractJSDocParseResult(
|
|
1083
1352
|
typeAlias,
|
|
1084
1353
|
file,
|
|
1085
|
-
makeParseOptions(extensionRegistry)
|
|
1354
|
+
makeParseOptions(extensionRegistry, void 0, checker, aliasType, aliasType)
|
|
1086
1355
|
);
|
|
1356
|
+
const annotations = [...typeAliasDoc.annotations];
|
|
1357
|
+
diagnostics.push(...typeAliasDoc.diagnostics);
|
|
1087
1358
|
const visiting = /* @__PURE__ */ new Set();
|
|
1088
1359
|
for (const member of typeAlias.type.members) {
|
|
1089
1360
|
if (ts3.isPropertySignature(member)) {
|
|
@@ -1093,6 +1364,8 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
1093
1364
|
file,
|
|
1094
1365
|
typeRegistry,
|
|
1095
1366
|
visiting,
|
|
1367
|
+
diagnostics,
|
|
1368
|
+
aliasType,
|
|
1096
1369
|
extensionRegistry
|
|
1097
1370
|
);
|
|
1098
1371
|
if (fieldNode) {
|
|
@@ -1108,12 +1381,13 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
1108
1381
|
fieldLayouts: fields.map(() => ({})),
|
|
1109
1382
|
typeRegistry,
|
|
1110
1383
|
...annotations.length > 0 && { annotations },
|
|
1384
|
+
...diagnostics.length > 0 && { diagnostics },
|
|
1111
1385
|
instanceMethods: [],
|
|
1112
1386
|
staticMethods: []
|
|
1113
1387
|
}
|
|
1114
1388
|
};
|
|
1115
1389
|
}
|
|
1116
|
-
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1390
|
+
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
|
|
1117
1391
|
if (!ts3.isIdentifier(prop.name)) {
|
|
1118
1392
|
return null;
|
|
1119
1393
|
}
|
|
@@ -1128,7 +1402,8 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
|
|
|
1128
1402
|
typeRegistry,
|
|
1129
1403
|
visiting,
|
|
1130
1404
|
prop,
|
|
1131
|
-
extensionRegistry
|
|
1405
|
+
extensionRegistry,
|
|
1406
|
+
diagnostics
|
|
1132
1407
|
);
|
|
1133
1408
|
const constraints = [];
|
|
1134
1409
|
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
@@ -1136,13 +1411,15 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
|
|
|
1136
1411
|
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
1137
1412
|
);
|
|
1138
1413
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1414
|
+
const docResult = extractJSDocParseResult(
|
|
1415
|
+
prop,
|
|
1416
|
+
file,
|
|
1417
|
+
makeParseOptions(extensionRegistry, type, checker, tsType, hostType)
|
|
1141
1418
|
);
|
|
1419
|
+
constraints.push(...docResult.constraints);
|
|
1420
|
+
diagnostics.push(...docResult.diagnostics);
|
|
1142
1421
|
let annotations = [];
|
|
1143
|
-
annotations.push(
|
|
1144
|
-
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
1145
|
-
);
|
|
1422
|
+
annotations.push(...docResult.annotations);
|
|
1146
1423
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
1147
1424
|
if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
|
|
1148
1425
|
annotations.push(defaultAnnotation);
|
|
@@ -1158,7 +1435,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
|
|
|
1158
1435
|
provenance
|
|
1159
1436
|
};
|
|
1160
1437
|
}
|
|
1161
|
-
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1438
|
+
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
|
|
1162
1439
|
if (!ts3.isIdentifier(prop.name)) {
|
|
1163
1440
|
return null;
|
|
1164
1441
|
}
|
|
@@ -1173,7 +1450,8 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
1173
1450
|
typeRegistry,
|
|
1174
1451
|
visiting,
|
|
1175
1452
|
prop,
|
|
1176
|
-
extensionRegistry
|
|
1453
|
+
extensionRegistry,
|
|
1454
|
+
diagnostics
|
|
1177
1455
|
);
|
|
1178
1456
|
const constraints = [];
|
|
1179
1457
|
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
@@ -1181,13 +1459,15 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
1181
1459
|
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
1182
1460
|
);
|
|
1183
1461
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1462
|
+
const docResult = extractJSDocParseResult(
|
|
1463
|
+
prop,
|
|
1464
|
+
file,
|
|
1465
|
+
makeParseOptions(extensionRegistry, type, checker, tsType, hostType)
|
|
1186
1466
|
);
|
|
1467
|
+
constraints.push(...docResult.constraints);
|
|
1468
|
+
diagnostics.push(...docResult.diagnostics);
|
|
1187
1469
|
let annotations = [];
|
|
1188
|
-
annotations.push(
|
|
1189
|
-
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
1190
|
-
);
|
|
1470
|
+
annotations.push(...docResult.annotations);
|
|
1191
1471
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
1192
1472
|
return {
|
|
1193
1473
|
kind: "field",
|
|
@@ -1316,7 +1596,7 @@ function getTypeNodeRegistrationName(typeNode) {
|
|
|
1316
1596
|
}
|
|
1317
1597
|
return null;
|
|
1318
1598
|
}
|
|
1319
|
-
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1599
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
1320
1600
|
const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
|
|
1321
1601
|
if (customType) {
|
|
1322
1602
|
return customType;
|
|
@@ -1328,7 +1608,8 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
1328
1608
|
typeRegistry,
|
|
1329
1609
|
visiting,
|
|
1330
1610
|
sourceNode,
|
|
1331
|
-
extensionRegistry
|
|
1611
|
+
extensionRegistry,
|
|
1612
|
+
diagnostics
|
|
1332
1613
|
);
|
|
1333
1614
|
if (primitiveAlias) {
|
|
1334
1615
|
return primitiveAlias;
|
|
@@ -1371,7 +1652,8 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
1371
1652
|
typeRegistry,
|
|
1372
1653
|
visiting,
|
|
1373
1654
|
sourceNode,
|
|
1374
|
-
extensionRegistry
|
|
1655
|
+
extensionRegistry,
|
|
1656
|
+
diagnostics
|
|
1375
1657
|
);
|
|
1376
1658
|
}
|
|
1377
1659
|
if (checker.isArrayType(type)) {
|
|
@@ -1382,15 +1664,24 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
1382
1664
|
typeRegistry,
|
|
1383
1665
|
visiting,
|
|
1384
1666
|
sourceNode,
|
|
1385
|
-
extensionRegistry
|
|
1667
|
+
extensionRegistry,
|
|
1668
|
+
diagnostics
|
|
1386
1669
|
);
|
|
1387
1670
|
}
|
|
1388
1671
|
if (isObjectType(type)) {
|
|
1389
|
-
return resolveObjectType(
|
|
1672
|
+
return resolveObjectType(
|
|
1673
|
+
type,
|
|
1674
|
+
checker,
|
|
1675
|
+
file,
|
|
1676
|
+
typeRegistry,
|
|
1677
|
+
visiting,
|
|
1678
|
+
extensionRegistry,
|
|
1679
|
+
diagnostics
|
|
1680
|
+
);
|
|
1390
1681
|
}
|
|
1391
1682
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1392
1683
|
}
|
|
1393
|
-
function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1684
|
+
function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
1394
1685
|
if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
|
|
1395
1686
|
return null;
|
|
1396
1687
|
}
|
|
@@ -1418,7 +1709,8 @@ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiti
|
|
|
1418
1709
|
file,
|
|
1419
1710
|
typeRegistry,
|
|
1420
1711
|
visiting,
|
|
1421
|
-
extensionRegistry
|
|
1712
|
+
extensionRegistry,
|
|
1713
|
+
diagnostics
|
|
1422
1714
|
),
|
|
1423
1715
|
...constraints.length > 0 && { constraints },
|
|
1424
1716
|
...annotations.length > 0 && { annotations },
|
|
@@ -1445,7 +1737,7 @@ function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
|
|
|
1445
1737
|
const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
|
|
1446
1738
|
return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
|
|
1447
1739
|
}
|
|
1448
|
-
function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1740
|
+
function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
1449
1741
|
const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
1450
1742
|
if (nestedAliasDecl !== void 0) {
|
|
1451
1743
|
return resolveAliasedPrimitiveTarget(
|
|
@@ -1454,12 +1746,22 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
|
|
|
1454
1746
|
file,
|
|
1455
1747
|
typeRegistry,
|
|
1456
1748
|
visiting,
|
|
1457
|
-
extensionRegistry
|
|
1749
|
+
extensionRegistry,
|
|
1750
|
+
diagnostics
|
|
1458
1751
|
);
|
|
1459
1752
|
}
|
|
1460
|
-
return resolveTypeNode(
|
|
1753
|
+
return resolveTypeNode(
|
|
1754
|
+
type,
|
|
1755
|
+
checker,
|
|
1756
|
+
file,
|
|
1757
|
+
typeRegistry,
|
|
1758
|
+
visiting,
|
|
1759
|
+
void 0,
|
|
1760
|
+
extensionRegistry,
|
|
1761
|
+
diagnostics
|
|
1762
|
+
);
|
|
1461
1763
|
}
|
|
1462
|
-
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1764
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
1463
1765
|
const typeName = getNamedTypeName(type);
|
|
1464
1766
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
1465
1767
|
if (typeName && typeName in typeRegistry) {
|
|
@@ -1549,7 +1851,8 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1549
1851
|
typeRegistry,
|
|
1550
1852
|
visiting,
|
|
1551
1853
|
nonNullMembers[0].sourceNode ?? sourceNode,
|
|
1552
|
-
extensionRegistry
|
|
1854
|
+
extensionRegistry,
|
|
1855
|
+
diagnostics
|
|
1553
1856
|
);
|
|
1554
1857
|
const result = hasNull ? {
|
|
1555
1858
|
kind: "union",
|
|
@@ -1565,7 +1868,8 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1565
1868
|
typeRegistry,
|
|
1566
1869
|
visiting,
|
|
1567
1870
|
memberSourceNode ?? sourceNode,
|
|
1568
|
-
extensionRegistry
|
|
1871
|
+
extensionRegistry,
|
|
1872
|
+
diagnostics
|
|
1569
1873
|
)
|
|
1570
1874
|
);
|
|
1571
1875
|
if (hasNull) {
|
|
@@ -1573,7 +1877,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1573
1877
|
}
|
|
1574
1878
|
return registerNamed({ kind: "union", members });
|
|
1575
1879
|
}
|
|
1576
|
-
function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1880
|
+
function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
1577
1881
|
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
1578
1882
|
const elementType = typeArgs?.[0];
|
|
1579
1883
|
const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
|
|
@@ -1584,11 +1888,12 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1584
1888
|
typeRegistry,
|
|
1585
1889
|
visiting,
|
|
1586
1890
|
elementSourceNode,
|
|
1587
|
-
extensionRegistry
|
|
1891
|
+
extensionRegistry,
|
|
1892
|
+
diagnostics
|
|
1588
1893
|
) : { kind: "primitive", primitiveKind: "string" };
|
|
1589
1894
|
return { kind: "array", items };
|
|
1590
1895
|
}
|
|
1591
|
-
function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1896
|
+
function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
1592
1897
|
if (type.getProperties().length > 0) {
|
|
1593
1898
|
return null;
|
|
1594
1899
|
}
|
|
@@ -1603,7 +1908,8 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, exten
|
|
|
1603
1908
|
typeRegistry,
|
|
1604
1909
|
visiting,
|
|
1605
1910
|
void 0,
|
|
1606
|
-
extensionRegistry
|
|
1911
|
+
extensionRegistry,
|
|
1912
|
+
diagnostics
|
|
1607
1913
|
);
|
|
1608
1914
|
return { kind: "record", valueType };
|
|
1609
1915
|
}
|
|
@@ -1632,7 +1938,7 @@ function typeNodeContainsReference(type, targetName) {
|
|
|
1632
1938
|
}
|
|
1633
1939
|
}
|
|
1634
1940
|
}
|
|
1635
|
-
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1941
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
1636
1942
|
const typeName = getNamedTypeName(type);
|
|
1637
1943
|
const namedTypeName = typeName ?? void 0;
|
|
1638
1944
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
@@ -1669,7 +1975,8 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
1669
1975
|
file,
|
|
1670
1976
|
typeRegistry,
|
|
1671
1977
|
visiting,
|
|
1672
|
-
extensionRegistry
|
|
1978
|
+
extensionRegistry,
|
|
1979
|
+
diagnostics
|
|
1673
1980
|
);
|
|
1674
1981
|
if (recordNode) {
|
|
1675
1982
|
visiting.delete(type);
|
|
@@ -1697,6 +2004,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
1697
2004
|
file,
|
|
1698
2005
|
typeRegistry,
|
|
1699
2006
|
visiting,
|
|
2007
|
+
diagnostics ?? [],
|
|
1700
2008
|
extensionRegistry
|
|
1701
2009
|
);
|
|
1702
2010
|
for (const prop of type.getProperties()) {
|
|
@@ -1711,7 +2019,8 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
1711
2019
|
typeRegistry,
|
|
1712
2020
|
visiting,
|
|
1713
2021
|
declaration,
|
|
1714
|
-
extensionRegistry
|
|
2022
|
+
extensionRegistry,
|
|
2023
|
+
diagnostics
|
|
1715
2024
|
);
|
|
1716
2025
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
1717
2026
|
properties.push({
|
|
@@ -1741,7 +2050,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
1741
2050
|
}
|
|
1742
2051
|
return objectNode;
|
|
1743
2052
|
}
|
|
1744
|
-
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2053
|
+
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, diagnostics, extensionRegistry) {
|
|
1745
2054
|
const symbols = [type.getSymbol(), type.aliasSymbol].filter(
|
|
1746
2055
|
(s) => s?.declarations != null && s.declarations.length > 0
|
|
1747
2056
|
);
|
|
@@ -1751,6 +2060,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1751
2060
|
const classDecl = declarations.find(ts3.isClassDeclaration);
|
|
1752
2061
|
if (classDecl) {
|
|
1753
2062
|
const map = /* @__PURE__ */ new Map();
|
|
2063
|
+
const hostType = checker.getTypeAtLocation(classDecl);
|
|
1754
2064
|
for (const member of classDecl.members) {
|
|
1755
2065
|
if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name)) {
|
|
1756
2066
|
const fieldNode = analyzeFieldToIR(
|
|
@@ -1759,6 +2069,8 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1759
2069
|
file,
|
|
1760
2070
|
typeRegistry,
|
|
1761
2071
|
visiting,
|
|
2072
|
+
diagnostics,
|
|
2073
|
+
hostType,
|
|
1762
2074
|
extensionRegistry
|
|
1763
2075
|
);
|
|
1764
2076
|
if (fieldNode) {
|
|
@@ -1780,6 +2092,8 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1780
2092
|
file,
|
|
1781
2093
|
typeRegistry,
|
|
1782
2094
|
visiting,
|
|
2095
|
+
checker.getTypeAtLocation(interfaceDecl),
|
|
2096
|
+
diagnostics,
|
|
1783
2097
|
extensionRegistry
|
|
1784
2098
|
);
|
|
1785
2099
|
}
|
|
@@ -1791,6 +2105,8 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1791
2105
|
file,
|
|
1792
2106
|
typeRegistry,
|
|
1793
2107
|
visiting,
|
|
2108
|
+
checker.getTypeAtLocation(typeAliasDecl),
|
|
2109
|
+
diagnostics,
|
|
1794
2110
|
extensionRegistry
|
|
1795
2111
|
);
|
|
1796
2112
|
}
|
|
@@ -1840,7 +2156,7 @@ function isNullishTypeNode(typeNode) {
|
|
|
1840
2156
|
}
|
|
1841
2157
|
return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
|
|
1842
2158
|
}
|
|
1843
|
-
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2159
|
+
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, hostType, diagnostics, extensionRegistry) {
|
|
1844
2160
|
const map = /* @__PURE__ */ new Map();
|
|
1845
2161
|
for (const member of members) {
|
|
1846
2162
|
if (ts3.isPropertySignature(member)) {
|
|
@@ -1850,6 +2166,8 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, e
|
|
|
1850
2166
|
file,
|
|
1851
2167
|
typeRegistry,
|
|
1852
2168
|
visiting,
|
|
2169
|
+
diagnostics,
|
|
2170
|
+
hostType,
|
|
1853
2171
|
extensionRegistry
|
|
1854
2172
|
);
|
|
1855
2173
|
if (fieldNode) {
|
|
@@ -2714,760 +3032,44 @@ function generateUiSchemaFromIR(ir) {
|
|
|
2714
3032
|
}
|
|
2715
3033
|
|
|
2716
3034
|
// src/validate/constraint-validator.ts
|
|
2717
|
-
import {
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
ctx.diagnostics.push({
|
|
2729
|
-
code: "TYPE_MISMATCH",
|
|
2730
|
-
message,
|
|
2731
|
-
severity: "error",
|
|
2732
|
-
primaryLocation: primary,
|
|
2733
|
-
relatedLocations: []
|
|
2734
|
-
});
|
|
2735
|
-
}
|
|
2736
|
-
function addUnknownExtension(ctx, message, primary) {
|
|
2737
|
-
ctx.diagnostics.push({
|
|
2738
|
-
code: "UNKNOWN_EXTENSION",
|
|
2739
|
-
message,
|
|
2740
|
-
severity: "warning",
|
|
2741
|
-
primaryLocation: primary,
|
|
2742
|
-
relatedLocations: []
|
|
2743
|
-
});
|
|
2744
|
-
}
|
|
2745
|
-
function addUnknownPathTarget(ctx, message, primary) {
|
|
2746
|
-
ctx.diagnostics.push({
|
|
2747
|
-
code: "UNKNOWN_PATH_TARGET",
|
|
2748
|
-
message,
|
|
2749
|
-
severity: "error",
|
|
2750
|
-
primaryLocation: primary,
|
|
2751
|
-
relatedLocations: []
|
|
2752
|
-
});
|
|
2753
|
-
}
|
|
2754
|
-
function addConstraintBroadening(ctx, message, primary, related) {
|
|
2755
|
-
ctx.diagnostics.push({
|
|
2756
|
-
code: "CONSTRAINT_BROADENING",
|
|
2757
|
-
message,
|
|
2758
|
-
severity: "error",
|
|
2759
|
-
primaryLocation: primary,
|
|
2760
|
-
relatedLocations: [related]
|
|
2761
|
-
});
|
|
2762
|
-
}
|
|
2763
|
-
function getExtensionIdFromConstraintId(constraintId) {
|
|
2764
|
-
const separator = constraintId.lastIndexOf("/");
|
|
2765
|
-
if (separator <= 0) {
|
|
2766
|
-
return null;
|
|
2767
|
-
}
|
|
2768
|
-
return constraintId.slice(0, separator);
|
|
2769
|
-
}
|
|
2770
|
-
function findNumeric(constraints, constraintKind) {
|
|
2771
|
-
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
2772
|
-
}
|
|
2773
|
-
function findLength(constraints, constraintKind) {
|
|
2774
|
-
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
2775
|
-
}
|
|
2776
|
-
function findAllowedMembers(constraints) {
|
|
2777
|
-
return constraints.filter(
|
|
2778
|
-
(c) => c.constraintKind === "allowedMembers"
|
|
2779
|
-
);
|
|
2780
|
-
}
|
|
2781
|
-
function findConstConstraints(constraints) {
|
|
2782
|
-
return constraints.filter(
|
|
2783
|
-
(c) => c.constraintKind === "const"
|
|
2784
|
-
);
|
|
2785
|
-
}
|
|
2786
|
-
function jsonValueEquals(left, right) {
|
|
2787
|
-
if (left === right) {
|
|
2788
|
-
return true;
|
|
2789
|
-
}
|
|
2790
|
-
if (Array.isArray(left) || Array.isArray(right)) {
|
|
2791
|
-
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
|
2792
|
-
return false;
|
|
2793
|
-
}
|
|
2794
|
-
return left.every((item, index) => jsonValueEquals(item, right[index]));
|
|
2795
|
-
}
|
|
2796
|
-
if (isJsonObject(left) || isJsonObject(right)) {
|
|
2797
|
-
if (!isJsonObject(left) || !isJsonObject(right)) {
|
|
2798
|
-
return false;
|
|
2799
|
-
}
|
|
2800
|
-
const leftKeys = Object.keys(left).sort();
|
|
2801
|
-
const rightKeys = Object.keys(right).sort();
|
|
2802
|
-
if (leftKeys.length !== rightKeys.length) {
|
|
2803
|
-
return false;
|
|
2804
|
-
}
|
|
2805
|
-
return leftKeys.every((key, index) => {
|
|
2806
|
-
const rightKey = rightKeys[index];
|
|
2807
|
-
if (rightKey !== key) {
|
|
2808
|
-
return false;
|
|
2809
|
-
}
|
|
2810
|
-
const leftValue = left[key];
|
|
2811
|
-
const rightValue = right[rightKey];
|
|
2812
|
-
return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
|
|
2813
|
-
});
|
|
2814
|
-
}
|
|
2815
|
-
return false;
|
|
2816
|
-
}
|
|
2817
|
-
function isJsonObject(value) {
|
|
2818
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2819
|
-
}
|
|
2820
|
-
function isOrderedBoundConstraint(constraint) {
|
|
2821
|
-
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";
|
|
2822
|
-
}
|
|
2823
|
-
function pathKey(constraint) {
|
|
2824
|
-
return constraint.path?.segments.join(".") ?? "";
|
|
2825
|
-
}
|
|
2826
|
-
function orderedBoundFamily(kind) {
|
|
2827
|
-
switch (kind) {
|
|
2828
|
-
case "minimum":
|
|
2829
|
-
case "exclusiveMinimum":
|
|
2830
|
-
return "numeric-lower";
|
|
2831
|
-
case "maximum":
|
|
2832
|
-
case "exclusiveMaximum":
|
|
2833
|
-
return "numeric-upper";
|
|
2834
|
-
case "minLength":
|
|
2835
|
-
return "minLength";
|
|
2836
|
-
case "minItems":
|
|
2837
|
-
return "minItems";
|
|
2838
|
-
case "maxLength":
|
|
2839
|
-
return "maxLength";
|
|
2840
|
-
case "maxItems":
|
|
2841
|
-
return "maxItems";
|
|
2842
|
-
default: {
|
|
2843
|
-
const _exhaustive = kind;
|
|
2844
|
-
return _exhaustive;
|
|
2845
|
-
}
|
|
2846
|
-
}
|
|
2847
|
-
}
|
|
2848
|
-
function isNumericLowerKind(kind) {
|
|
2849
|
-
return kind === "minimum" || kind === "exclusiveMinimum";
|
|
2850
|
-
}
|
|
2851
|
-
function isNumericUpperKind(kind) {
|
|
2852
|
-
return kind === "maximum" || kind === "exclusiveMaximum";
|
|
2853
|
-
}
|
|
2854
|
-
function describeConstraintTag(constraint) {
|
|
2855
|
-
return `@${constraint.constraintKind}`;
|
|
2856
|
-
}
|
|
2857
|
-
function compareConstraintStrength(current, previous) {
|
|
2858
|
-
const family = orderedBoundFamily(current.constraintKind);
|
|
2859
|
-
if (family === "numeric-lower") {
|
|
2860
|
-
if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
|
|
2861
|
-
throw new Error("numeric-lower family received non-numeric lower-bound constraint");
|
|
2862
|
-
}
|
|
2863
|
-
if (current.value !== previous.value) {
|
|
2864
|
-
return current.value > previous.value ? 1 : -1;
|
|
2865
|
-
}
|
|
2866
|
-
if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
|
|
2867
|
-
return 1;
|
|
2868
|
-
}
|
|
2869
|
-
if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
|
|
2870
|
-
return -1;
|
|
2871
|
-
}
|
|
2872
|
-
return 0;
|
|
2873
|
-
}
|
|
2874
|
-
if (family === "numeric-upper") {
|
|
2875
|
-
if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
|
|
2876
|
-
throw new Error("numeric-upper family received non-numeric upper-bound constraint");
|
|
2877
|
-
}
|
|
2878
|
-
if (current.value !== previous.value) {
|
|
2879
|
-
return current.value < previous.value ? 1 : -1;
|
|
2880
|
-
}
|
|
2881
|
-
if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
|
|
2882
|
-
return 1;
|
|
2883
|
-
}
|
|
2884
|
-
if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
|
|
2885
|
-
return -1;
|
|
2886
|
-
}
|
|
2887
|
-
return 0;
|
|
2888
|
-
}
|
|
2889
|
-
switch (family) {
|
|
2890
|
-
case "minLength":
|
|
2891
|
-
case "minItems":
|
|
2892
|
-
if (current.value === previous.value) {
|
|
2893
|
-
return 0;
|
|
2894
|
-
}
|
|
2895
|
-
return current.value > previous.value ? 1 : -1;
|
|
2896
|
-
case "maxLength":
|
|
2897
|
-
case "maxItems":
|
|
2898
|
-
if (current.value === previous.value) {
|
|
2899
|
-
return 0;
|
|
2900
|
-
}
|
|
2901
|
-
return current.value < previous.value ? 1 : -1;
|
|
2902
|
-
default: {
|
|
2903
|
-
const _exhaustive = family;
|
|
2904
|
-
return _exhaustive;
|
|
2905
|
-
}
|
|
2906
|
-
}
|
|
2907
|
-
}
|
|
2908
|
-
function checkConstraintBroadening(ctx, fieldName, constraints) {
|
|
2909
|
-
const strongestByKey = /* @__PURE__ */ new Map();
|
|
2910
|
-
for (const constraint of constraints) {
|
|
2911
|
-
if (!isOrderedBoundConstraint(constraint)) {
|
|
2912
|
-
continue;
|
|
2913
|
-
}
|
|
2914
|
-
const key = `${orderedBoundFamily(constraint.constraintKind)}:${pathKey(constraint)}`;
|
|
2915
|
-
const previous = strongestByKey.get(key);
|
|
2916
|
-
if (previous === void 0) {
|
|
2917
|
-
strongestByKey.set(key, constraint);
|
|
2918
|
-
continue;
|
|
2919
|
-
}
|
|
2920
|
-
const strength = compareConstraintStrength(constraint, previous);
|
|
2921
|
-
if (strength < 0) {
|
|
2922
|
-
const displayFieldName = formatPathTargetFieldName(
|
|
2923
|
-
fieldName,
|
|
2924
|
-
constraint.path?.segments ?? []
|
|
2925
|
-
);
|
|
2926
|
-
addConstraintBroadening(
|
|
2927
|
-
ctx,
|
|
2928
|
-
`Field "${displayFieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
|
|
2929
|
-
constraint.provenance,
|
|
2930
|
-
previous.provenance
|
|
2931
|
-
);
|
|
2932
|
-
continue;
|
|
2933
|
-
}
|
|
2934
|
-
if (strength <= 0) {
|
|
2935
|
-
continue;
|
|
2936
|
-
}
|
|
2937
|
-
strongestByKey.set(key, constraint);
|
|
2938
|
-
}
|
|
2939
|
-
}
|
|
2940
|
-
function compareCustomConstraintStrength(current, previous) {
|
|
2941
|
-
const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
|
|
2942
|
-
const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
|
|
2943
|
-
switch (current.role.bound) {
|
|
2944
|
-
case "lower":
|
|
2945
|
-
return equalPayloadTiebreaker;
|
|
2946
|
-
case "upper":
|
|
2947
|
-
return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
|
|
2948
|
-
case "exact":
|
|
2949
|
-
return order === 0 ? 0 : Number.NaN;
|
|
2950
|
-
default: {
|
|
2951
|
-
const _exhaustive = current.role.bound;
|
|
2952
|
-
return _exhaustive;
|
|
2953
|
-
}
|
|
2954
|
-
}
|
|
2955
|
-
}
|
|
2956
|
-
function compareSemanticInclusivity(currentInclusive, previousInclusive) {
|
|
2957
|
-
if (currentInclusive === previousInclusive) {
|
|
2958
|
-
return 0;
|
|
2959
|
-
}
|
|
2960
|
-
return currentInclusive ? -1 : 1;
|
|
2961
|
-
}
|
|
2962
|
-
function customConstraintsContradict(lower, upper) {
|
|
2963
|
-
const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
|
|
2964
|
-
if (order > 0) {
|
|
2965
|
-
return true;
|
|
2966
|
-
}
|
|
2967
|
-
if (order < 0) {
|
|
2968
|
-
return false;
|
|
2969
|
-
}
|
|
2970
|
-
return !lower.role.inclusive || !upper.role.inclusive;
|
|
2971
|
-
}
|
|
2972
|
-
function describeCustomConstraintTag(constraint) {
|
|
2973
|
-
return constraint.provenance.tagName ?? constraint.constraintId;
|
|
2974
|
-
}
|
|
2975
|
-
function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
|
|
2976
|
-
if (ctx.extensionRegistry === void 0) {
|
|
2977
|
-
return;
|
|
2978
|
-
}
|
|
2979
|
-
const strongestByKey = /* @__PURE__ */ new Map();
|
|
2980
|
-
const lowerByFamily = /* @__PURE__ */ new Map();
|
|
2981
|
-
const upperByFamily = /* @__PURE__ */ new Map();
|
|
2982
|
-
for (const constraint of constraints) {
|
|
2983
|
-
if (constraint.constraintKind !== "custom") {
|
|
2984
|
-
continue;
|
|
2985
|
-
}
|
|
2986
|
-
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
2987
|
-
if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
|
|
2988
|
-
continue;
|
|
2989
|
-
}
|
|
2990
|
-
const entry = {
|
|
2991
|
-
constraint,
|
|
2992
|
-
comparePayloads: registration.comparePayloads,
|
|
2993
|
-
role: registration.semanticRole
|
|
2994
|
-
};
|
|
2995
|
-
const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
|
|
2996
|
-
const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
|
|
2997
|
-
const previous = strongestByKey.get(boundKey);
|
|
2998
|
-
if (previous !== void 0) {
|
|
2999
|
-
const strength = compareCustomConstraintStrength(entry, previous);
|
|
3000
|
-
if (Number.isNaN(strength)) {
|
|
3001
|
-
addContradiction(
|
|
3002
|
-
ctx,
|
|
3003
|
-
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
|
|
3004
|
-
constraint.provenance,
|
|
3005
|
-
previous.constraint.provenance
|
|
3006
|
-
);
|
|
3007
|
-
continue;
|
|
3008
|
-
}
|
|
3009
|
-
if (strength < 0) {
|
|
3010
|
-
addConstraintBroadening(
|
|
3011
|
-
ctx,
|
|
3012
|
-
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
|
|
3013
|
-
constraint.provenance,
|
|
3014
|
-
previous.constraint.provenance
|
|
3015
|
-
);
|
|
3016
|
-
continue;
|
|
3017
|
-
}
|
|
3018
|
-
if (strength > 0) {
|
|
3019
|
-
strongestByKey.set(boundKey, entry);
|
|
3020
|
-
}
|
|
3021
|
-
} else {
|
|
3022
|
-
strongestByKey.set(boundKey, entry);
|
|
3023
|
-
}
|
|
3024
|
-
if (registration.semanticRole.bound === "lower") {
|
|
3025
|
-
lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
3026
|
-
} else if (registration.semanticRole.bound === "upper") {
|
|
3027
|
-
upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
3028
|
-
}
|
|
3029
|
-
}
|
|
3030
|
-
for (const [familyKey, lower] of lowerByFamily) {
|
|
3031
|
-
const upper = upperByFamily.get(familyKey);
|
|
3032
|
-
if (upper === void 0) {
|
|
3033
|
-
continue;
|
|
3034
|
-
}
|
|
3035
|
-
if (!customConstraintsContradict(lower, upper)) {
|
|
3036
|
-
continue;
|
|
3037
|
-
}
|
|
3038
|
-
addContradiction(
|
|
3039
|
-
ctx,
|
|
3040
|
-
`Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
|
|
3041
|
-
lower.constraint.provenance,
|
|
3042
|
-
upper.constraint.provenance
|
|
3043
|
-
);
|
|
3044
|
-
}
|
|
3045
|
-
}
|
|
3046
|
-
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
3047
|
-
const min = findNumeric(constraints, "minimum");
|
|
3048
|
-
const max = findNumeric(constraints, "maximum");
|
|
3049
|
-
const exMin = findNumeric(constraints, "exclusiveMinimum");
|
|
3050
|
-
const exMax = findNumeric(constraints, "exclusiveMaximum");
|
|
3051
|
-
if (min !== void 0 && max !== void 0 && min.value > max.value) {
|
|
3052
|
-
addContradiction(
|
|
3053
|
-
ctx,
|
|
3054
|
-
`Field "${fieldName}": minimum (${String(min.value)}) is greater than maximum (${String(max.value)})`,
|
|
3055
|
-
min.provenance,
|
|
3056
|
-
max.provenance
|
|
3057
|
-
);
|
|
3058
|
-
}
|
|
3059
|
-
if (exMin !== void 0 && max !== void 0 && exMin.value >= max.value) {
|
|
3060
|
-
addContradiction(
|
|
3061
|
-
ctx,
|
|
3062
|
-
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to maximum (${String(max.value)})`,
|
|
3063
|
-
exMin.provenance,
|
|
3064
|
-
max.provenance
|
|
3065
|
-
);
|
|
3066
|
-
}
|
|
3067
|
-
if (min !== void 0 && exMax !== void 0 && min.value >= exMax.value) {
|
|
3068
|
-
addContradiction(
|
|
3069
|
-
ctx,
|
|
3070
|
-
`Field "${fieldName}": minimum (${String(min.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
3071
|
-
min.provenance,
|
|
3072
|
-
exMax.provenance
|
|
3073
|
-
);
|
|
3074
|
-
}
|
|
3075
|
-
if (exMin !== void 0 && exMax !== void 0 && exMin.value >= exMax.value) {
|
|
3076
|
-
addContradiction(
|
|
3077
|
-
ctx,
|
|
3078
|
-
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
3079
|
-
exMin.provenance,
|
|
3080
|
-
exMax.provenance
|
|
3081
|
-
);
|
|
3082
|
-
}
|
|
3083
|
-
}
|
|
3084
|
-
function checkLengthContradictions(ctx, fieldName, constraints) {
|
|
3085
|
-
const minLen = findLength(constraints, "minLength");
|
|
3086
|
-
const maxLen = findLength(constraints, "maxLength");
|
|
3087
|
-
if (minLen !== void 0 && maxLen !== void 0 && minLen.value > maxLen.value) {
|
|
3088
|
-
addContradiction(
|
|
3089
|
-
ctx,
|
|
3090
|
-
`Field "${fieldName}": minLength (${String(minLen.value)}) is greater than maxLength (${String(maxLen.value)})`,
|
|
3091
|
-
minLen.provenance,
|
|
3092
|
-
maxLen.provenance
|
|
3093
|
-
);
|
|
3094
|
-
}
|
|
3095
|
-
const minItems = findLength(constraints, "minItems");
|
|
3096
|
-
const maxItems = findLength(constraints, "maxItems");
|
|
3097
|
-
if (minItems !== void 0 && maxItems !== void 0 && minItems.value > maxItems.value) {
|
|
3098
|
-
addContradiction(
|
|
3099
|
-
ctx,
|
|
3100
|
-
`Field "${fieldName}": minItems (${String(minItems.value)}) is greater than maxItems (${String(maxItems.value)})`,
|
|
3101
|
-
minItems.provenance,
|
|
3102
|
-
maxItems.provenance
|
|
3103
|
-
);
|
|
3104
|
-
}
|
|
3105
|
-
}
|
|
3106
|
-
function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
|
|
3107
|
-
const members = findAllowedMembers(constraints);
|
|
3108
|
-
if (members.length < 2) return;
|
|
3109
|
-
const firstSet = new Set(members[0]?.members ?? []);
|
|
3110
|
-
for (let i = 1; i < members.length; i++) {
|
|
3111
|
-
const current = members[i];
|
|
3112
|
-
if (current === void 0) continue;
|
|
3113
|
-
for (const m of firstSet) {
|
|
3114
|
-
if (!current.members.includes(m)) {
|
|
3115
|
-
firstSet.delete(m);
|
|
3116
|
-
}
|
|
3117
|
-
}
|
|
3118
|
-
}
|
|
3119
|
-
if (firstSet.size === 0) {
|
|
3120
|
-
const first = members[0];
|
|
3121
|
-
const second = members[1];
|
|
3122
|
-
if (first !== void 0 && second !== void 0) {
|
|
3123
|
-
addContradiction(
|
|
3124
|
-
ctx,
|
|
3125
|
-
`Field "${fieldName}": allowedMembers constraints have an empty intersection (no valid values remain)`,
|
|
3126
|
-
first.provenance,
|
|
3127
|
-
second.provenance
|
|
3128
|
-
);
|
|
3129
|
-
}
|
|
3130
|
-
}
|
|
3131
|
-
}
|
|
3132
|
-
function checkConstContradictions(ctx, fieldName, constraints) {
|
|
3133
|
-
const constConstraints = findConstConstraints(constraints);
|
|
3134
|
-
if (constConstraints.length < 2) return;
|
|
3135
|
-
const first = constConstraints[0];
|
|
3136
|
-
if (first === void 0) return;
|
|
3137
|
-
for (let i = 1; i < constConstraints.length; i++) {
|
|
3138
|
-
const current = constConstraints[i];
|
|
3139
|
-
if (current === void 0) continue;
|
|
3140
|
-
if (jsonValueEquals(first.value, current.value)) {
|
|
3141
|
-
continue;
|
|
3142
|
-
}
|
|
3143
|
-
addContradiction(
|
|
3144
|
-
ctx,
|
|
3145
|
-
`Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
|
|
3146
|
-
first.provenance,
|
|
3147
|
-
current.provenance
|
|
3148
|
-
);
|
|
3149
|
-
}
|
|
3150
|
-
}
|
|
3151
|
-
function typeLabel(type) {
|
|
3152
|
-
switch (type.kind) {
|
|
3153
|
-
case "primitive":
|
|
3154
|
-
return type.primitiveKind;
|
|
3155
|
-
case "enum":
|
|
3156
|
-
return "enum";
|
|
3157
|
-
case "array":
|
|
3158
|
-
return "array";
|
|
3159
|
-
case "object":
|
|
3160
|
-
return "object";
|
|
3161
|
-
case "record":
|
|
3162
|
-
return "record";
|
|
3163
|
-
case "union":
|
|
3164
|
-
return "union";
|
|
3165
|
-
case "reference":
|
|
3166
|
-
return `reference(${type.name})`;
|
|
3167
|
-
case "dynamic":
|
|
3168
|
-
return `dynamic(${type.dynamicKind})`;
|
|
3169
|
-
case "custom":
|
|
3170
|
-
return `custom(${type.typeId})`;
|
|
3171
|
-
default: {
|
|
3172
|
-
const _exhaustive = type;
|
|
3173
|
-
return String(_exhaustive);
|
|
3174
|
-
}
|
|
3175
|
-
}
|
|
3176
|
-
}
|
|
3177
|
-
function dereferenceType(ctx, type) {
|
|
3178
|
-
let current = type;
|
|
3179
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3180
|
-
while (current.kind === "reference") {
|
|
3181
|
-
if (seen.has(current.name)) {
|
|
3182
|
-
return current;
|
|
3183
|
-
}
|
|
3184
|
-
seen.add(current.name);
|
|
3185
|
-
const definition = ctx.typeRegistry[current.name];
|
|
3186
|
-
if (definition === void 0) {
|
|
3187
|
-
return current;
|
|
3188
|
-
}
|
|
3189
|
-
current = definition.type;
|
|
3190
|
-
}
|
|
3191
|
-
return current;
|
|
3192
|
-
}
|
|
3193
|
-
function collectReferencedTypeConstraints(ctx, type) {
|
|
3194
|
-
const collected = [];
|
|
3195
|
-
let current = type;
|
|
3196
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3197
|
-
while (current.kind === "reference") {
|
|
3198
|
-
if (seen.has(current.name)) {
|
|
3199
|
-
break;
|
|
3200
|
-
}
|
|
3201
|
-
seen.add(current.name);
|
|
3202
|
-
const definition = ctx.typeRegistry[current.name];
|
|
3203
|
-
if (definition === void 0) {
|
|
3204
|
-
break;
|
|
3205
|
-
}
|
|
3206
|
-
if (definition.constraints !== void 0) {
|
|
3207
|
-
collected.push(...definition.constraints);
|
|
3208
|
-
}
|
|
3209
|
-
current = definition.type;
|
|
3210
|
-
}
|
|
3211
|
-
return collected;
|
|
3212
|
-
}
|
|
3213
|
-
function resolvePathTargetType(ctx, type, segments) {
|
|
3214
|
-
const effectiveType = dereferenceType(ctx, type);
|
|
3215
|
-
if (segments.length === 0) {
|
|
3216
|
-
return { kind: "resolved", type: effectiveType };
|
|
3217
|
-
}
|
|
3218
|
-
if (effectiveType.kind === "array") {
|
|
3219
|
-
return resolvePathTargetType(ctx, effectiveType.items, segments);
|
|
3220
|
-
}
|
|
3221
|
-
if (effectiveType.kind === "object") {
|
|
3222
|
-
const [segment, ...rest] = segments;
|
|
3223
|
-
if (segment === void 0) {
|
|
3224
|
-
throw new Error("Invariant violation: object path traversal requires a segment");
|
|
3225
|
-
}
|
|
3226
|
-
const property = effectiveType.properties.find((prop) => prop.name === segment);
|
|
3227
|
-
if (property === void 0) {
|
|
3228
|
-
return { kind: "missing-property", segment };
|
|
3229
|
-
}
|
|
3230
|
-
return resolvePathTargetType(ctx, property.type, rest);
|
|
3231
|
-
}
|
|
3232
|
-
return { kind: "unresolvable", type: effectiveType };
|
|
3233
|
-
}
|
|
3234
|
-
function isNullType(type) {
|
|
3235
|
-
return type.kind === "primitive" && type.primitiveKind === "null";
|
|
3236
|
-
}
|
|
3237
|
-
function collectCustomConstraintCandidateTypes(ctx, type) {
|
|
3238
|
-
const effectiveType = dereferenceType(ctx, type);
|
|
3239
|
-
const candidates = [effectiveType];
|
|
3240
|
-
if (effectiveType.kind === "array") {
|
|
3241
|
-
candidates.push(...collectCustomConstraintCandidateTypes(ctx, effectiveType.items));
|
|
3242
|
-
}
|
|
3243
|
-
if (effectiveType.kind === "union") {
|
|
3244
|
-
const memberTypes = effectiveType.members.map((member) => dereferenceType(ctx, member));
|
|
3245
|
-
const nonNullMembers = memberTypes.filter((member) => !isNullType(member));
|
|
3246
|
-
if (nonNullMembers.length === 1 && nonNullMembers.length < memberTypes.length) {
|
|
3247
|
-
const [nullableMember] = nonNullMembers;
|
|
3248
|
-
if (nullableMember !== void 0) {
|
|
3249
|
-
candidates.push(...collectCustomConstraintCandidateTypes(ctx, nullableMember));
|
|
3250
|
-
}
|
|
3251
|
-
}
|
|
3252
|
-
}
|
|
3253
|
-
return candidates;
|
|
3254
|
-
}
|
|
3255
|
-
function formatPathTargetFieldName(fieldName, path2) {
|
|
3256
|
-
return path2.length === 0 ? fieldName : `${fieldName}.${path2.join(".")}`;
|
|
3257
|
-
}
|
|
3258
|
-
function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
3259
|
-
const effectiveType = dereferenceType(ctx, type);
|
|
3260
|
-
const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
|
|
3261
|
-
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
3262
|
-
const isArray = effectiveType.kind === "array";
|
|
3263
|
-
const isEnum = effectiveType.kind === "enum";
|
|
3264
|
-
const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
|
|
3265
|
-
const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
|
|
3266
|
-
const label = typeLabel(effectiveType);
|
|
3267
|
-
const ck = constraint.constraintKind;
|
|
3268
|
-
switch (ck) {
|
|
3269
|
-
case "minimum":
|
|
3270
|
-
case "maximum":
|
|
3271
|
-
case "exclusiveMinimum":
|
|
3272
|
-
case "exclusiveMaximum":
|
|
3273
|
-
case "multipleOf": {
|
|
3274
|
-
if (!isNumber) {
|
|
3275
|
-
addTypeMismatch(
|
|
3276
|
-
ctx,
|
|
3277
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
|
|
3278
|
-
constraint.provenance
|
|
3279
|
-
);
|
|
3280
|
-
}
|
|
3281
|
-
break;
|
|
3282
|
-
}
|
|
3283
|
-
case "minLength":
|
|
3284
|
-
case "maxLength":
|
|
3285
|
-
case "pattern": {
|
|
3286
|
-
if (!isString && !isStringArray) {
|
|
3287
|
-
addTypeMismatch(
|
|
3288
|
-
ctx,
|
|
3289
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
|
|
3290
|
-
constraint.provenance
|
|
3291
|
-
);
|
|
3292
|
-
}
|
|
3293
|
-
break;
|
|
3294
|
-
}
|
|
3295
|
-
case "minItems":
|
|
3296
|
-
case "maxItems":
|
|
3297
|
-
case "uniqueItems": {
|
|
3298
|
-
if (!isArray) {
|
|
3299
|
-
addTypeMismatch(
|
|
3300
|
-
ctx,
|
|
3301
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
|
|
3302
|
-
constraint.provenance
|
|
3303
|
-
);
|
|
3304
|
-
}
|
|
3305
|
-
break;
|
|
3306
|
-
}
|
|
3307
|
-
case "allowedMembers": {
|
|
3308
|
-
if (!isEnum) {
|
|
3309
|
-
addTypeMismatch(
|
|
3310
|
-
ctx,
|
|
3311
|
-
`Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
|
|
3312
|
-
constraint.provenance
|
|
3313
|
-
);
|
|
3314
|
-
}
|
|
3315
|
-
break;
|
|
3316
|
-
}
|
|
3317
|
-
case "const": {
|
|
3318
|
-
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
|
|
3319
|
-
effectiveType.primitiveKind
|
|
3320
|
-
) || effectiveType.kind === "enum";
|
|
3321
|
-
if (!isPrimitiveConstType) {
|
|
3322
|
-
addTypeMismatch(
|
|
3323
|
-
ctx,
|
|
3324
|
-
`Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
|
|
3325
|
-
constraint.provenance
|
|
3326
|
-
);
|
|
3327
|
-
break;
|
|
3328
|
-
}
|
|
3329
|
-
if (effectiveType.kind === "primitive") {
|
|
3330
|
-
const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
|
|
3331
|
-
const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
|
|
3332
|
-
if (valueType !== expectedValueType) {
|
|
3333
|
-
addTypeMismatch(
|
|
3334
|
-
ctx,
|
|
3335
|
-
`Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
|
|
3336
|
-
constraint.provenance
|
|
3337
|
-
);
|
|
3338
|
-
}
|
|
3339
|
-
break;
|
|
3340
|
-
}
|
|
3341
|
-
const memberValues = effectiveType.members.map((member) => member.value);
|
|
3342
|
-
if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
|
|
3343
|
-
addTypeMismatch(
|
|
3344
|
-
ctx,
|
|
3345
|
-
`Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
|
|
3346
|
-
constraint.provenance
|
|
3347
|
-
);
|
|
3348
|
-
}
|
|
3349
|
-
break;
|
|
3350
|
-
}
|
|
3351
|
-
case "custom": {
|
|
3352
|
-
checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
|
|
3353
|
-
break;
|
|
3354
|
-
}
|
|
3355
|
-
default: {
|
|
3356
|
-
const _exhaustive = constraint;
|
|
3357
|
-
throw new Error(
|
|
3358
|
-
`Unhandled constraint kind: ${_exhaustive.constraintKind}`
|
|
3359
|
-
);
|
|
3360
|
-
}
|
|
3361
|
-
}
|
|
3362
|
-
}
|
|
3363
|
-
function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
3364
|
-
for (const constraint of constraints) {
|
|
3365
|
-
if (constraint.path) {
|
|
3366
|
-
const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
|
|
3367
|
-
const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
|
|
3368
|
-
if (resolution.kind === "missing-property") {
|
|
3369
|
-
addUnknownPathTarget(
|
|
3370
|
-
ctx,
|
|
3371
|
-
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
|
|
3372
|
-
constraint.provenance
|
|
3373
|
-
);
|
|
3374
|
-
continue;
|
|
3375
|
-
}
|
|
3376
|
-
if (resolution.kind === "unresolvable") {
|
|
3377
|
-
addTypeMismatch(
|
|
3378
|
-
ctx,
|
|
3379
|
-
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(resolution.type)}" cannot be traversed`,
|
|
3380
|
-
constraint.provenance
|
|
3381
|
-
);
|
|
3382
|
-
continue;
|
|
3383
|
-
}
|
|
3384
|
-
checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
|
|
3385
|
-
continue;
|
|
3386
|
-
}
|
|
3387
|
-
checkConstraintOnType(ctx, fieldName, type, constraint);
|
|
3388
|
-
}
|
|
3389
|
-
}
|
|
3390
|
-
function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
3391
|
-
if (ctx.extensionRegistry === void 0) return;
|
|
3392
|
-
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
3393
|
-
if (registration === void 0) {
|
|
3394
|
-
addUnknownExtension(
|
|
3395
|
-
ctx,
|
|
3396
|
-
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not registered in the extension registry`,
|
|
3397
|
-
constraint.provenance
|
|
3398
|
-
);
|
|
3399
|
-
return;
|
|
3400
|
-
}
|
|
3401
|
-
const candidateTypes = collectCustomConstraintCandidateTypes(ctx, type);
|
|
3402
|
-
const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : normalizeConstraintTagName2(constraint.provenance.tagName.replace(/^@/, ""));
|
|
3403
|
-
if (normalizedTagName !== void 0) {
|
|
3404
|
-
const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
|
|
3405
|
-
const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
|
|
3406
|
-
if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && !candidateTypes.some(
|
|
3407
|
-
(candidateType) => tagRegistration.registration.isApplicableToType?.(candidateType) !== false
|
|
3408
|
-
)) {
|
|
3409
|
-
addTypeMismatch(
|
|
3410
|
-
ctx,
|
|
3411
|
-
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3412
|
-
constraint.provenance
|
|
3413
|
-
);
|
|
3414
|
-
return;
|
|
3415
|
-
}
|
|
3416
|
-
}
|
|
3417
|
-
if (registration.applicableTypes === null) {
|
|
3418
|
-
if (!candidateTypes.some((candidateType) => registration.isApplicableToType?.(candidateType) !== false)) {
|
|
3419
|
-
addTypeMismatch(
|
|
3420
|
-
ctx,
|
|
3421
|
-
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3422
|
-
constraint.provenance
|
|
3423
|
-
);
|
|
3035
|
+
import {
|
|
3036
|
+
analyzeConstraintTargets
|
|
3037
|
+
} from "@formspec/analysis";
|
|
3038
|
+
function validateFieldNode(ctx, field) {
|
|
3039
|
+
const analysis = analyzeConstraintTargets(
|
|
3040
|
+
field.name,
|
|
3041
|
+
field.type,
|
|
3042
|
+
field.constraints,
|
|
3043
|
+
ctx.typeRegistry,
|
|
3044
|
+
ctx.extensionRegistry === void 0 ? void 0 : {
|
|
3045
|
+
extensionRegistry: ctx.extensionRegistry
|
|
3424
3046
|
}
|
|
3425
|
-
return;
|
|
3426
|
-
}
|
|
3427
|
-
const applicableTypes = registration.applicableTypes;
|
|
3428
|
-
const matchesApplicableType = candidateTypes.some(
|
|
3429
|
-
(candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
|
|
3430
3047
|
);
|
|
3431
|
-
|
|
3432
|
-
addTypeMismatch(
|
|
3433
|
-
ctx,
|
|
3434
|
-
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3435
|
-
constraint.provenance
|
|
3436
|
-
);
|
|
3437
|
-
}
|
|
3438
|
-
}
|
|
3439
|
-
function validateFieldNode(ctx, field) {
|
|
3440
|
-
validateConstraints(ctx, field.name, field.type, [
|
|
3441
|
-
...collectReferencedTypeConstraints(ctx, field.type),
|
|
3442
|
-
...field.constraints
|
|
3443
|
-
]);
|
|
3048
|
+
ctx.diagnostics.push(...analysis.diagnostics);
|
|
3444
3049
|
if (field.type.kind === "object") {
|
|
3445
|
-
for (const
|
|
3446
|
-
validateObjectProperty(ctx, field.name,
|
|
3050
|
+
for (const property of field.type.properties) {
|
|
3051
|
+
validateObjectProperty(ctx, field.name, property);
|
|
3447
3052
|
}
|
|
3448
3053
|
}
|
|
3449
3054
|
}
|
|
3450
|
-
function validateObjectProperty(ctx, parentName,
|
|
3451
|
-
const qualifiedName = `${parentName}.${
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3055
|
+
function validateObjectProperty(ctx, parentName, property) {
|
|
3056
|
+
const qualifiedName = `${parentName}.${property.name}`;
|
|
3057
|
+
const analysis = analyzeConstraintTargets(
|
|
3058
|
+
qualifiedName,
|
|
3059
|
+
property.type,
|
|
3060
|
+
property.constraints,
|
|
3061
|
+
ctx.typeRegistry,
|
|
3062
|
+
ctx.extensionRegistry === void 0 ? void 0 : {
|
|
3063
|
+
extensionRegistry: ctx.extensionRegistry
|
|
3064
|
+
}
|
|
3065
|
+
);
|
|
3066
|
+
ctx.diagnostics.push(...analysis.diagnostics);
|
|
3067
|
+
if (property.type.kind === "object") {
|
|
3068
|
+
for (const nestedProperty of property.type.properties) {
|
|
3069
|
+
validateObjectProperty(ctx, qualifiedName, nestedProperty);
|
|
3459
3070
|
}
|
|
3460
3071
|
}
|
|
3461
3072
|
}
|
|
3462
|
-
function validateConstraints(ctx, name, type, constraints) {
|
|
3463
|
-
checkNumericContradictions(ctx, name, constraints);
|
|
3464
|
-
checkLengthContradictions(ctx, name, constraints);
|
|
3465
|
-
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
3466
|
-
checkConstContradictions(ctx, name, constraints);
|
|
3467
|
-
checkConstraintBroadening(ctx, name, constraints);
|
|
3468
|
-
checkCustomConstraintSemantics(ctx, name, constraints);
|
|
3469
|
-
checkTypeApplicability(ctx, name, type, constraints);
|
|
3470
|
-
}
|
|
3471
3073
|
function validateElement(ctx, element) {
|
|
3472
3074
|
switch (element.kind) {
|
|
3473
3075
|
case "field":
|
|
@@ -3484,8 +3086,8 @@ function validateElement(ctx, element) {
|
|
|
3484
3086
|
}
|
|
3485
3087
|
break;
|
|
3486
3088
|
default: {
|
|
3487
|
-
const
|
|
3488
|
-
throw new Error(`Unhandled element kind: ${
|
|
3089
|
+
const exhaustive = element;
|
|
3090
|
+
throw new Error(`Unhandled element kind: ${String(exhaustive)}`);
|
|
3489
3091
|
}
|
|
3490
3092
|
}
|
|
3491
3093
|
}
|
|
@@ -3500,12 +3102,18 @@ function validateIR(ir, options) {
|
|
|
3500
3102
|
}
|
|
3501
3103
|
return {
|
|
3502
3104
|
diagnostics: ctx.diagnostics,
|
|
3503
|
-
valid: ctx.diagnostics.every((
|
|
3105
|
+
valid: ctx.diagnostics.every((diagnostic) => diagnostic.severity !== "error")
|
|
3504
3106
|
};
|
|
3505
3107
|
}
|
|
3506
3108
|
|
|
3507
3109
|
// src/generators/class-schema.ts
|
|
3508
3110
|
function generateClassSchemas(analysis, source, options) {
|
|
3111
|
+
const errorDiagnostics = analysis.diagnostics?.filter(
|
|
3112
|
+
(diagnostic) => diagnostic.severity === "error"
|
|
3113
|
+
);
|
|
3114
|
+
if (errorDiagnostics !== void 0 && errorDiagnostics.length > 0) {
|
|
3115
|
+
throw new Error(formatValidationError(errorDiagnostics));
|
|
3116
|
+
}
|
|
3509
3117
|
const ir = canonicalizeTSDoc(analysis, source);
|
|
3510
3118
|
const validationResult = validateIR(ir, {
|
|
3511
3119
|
...options?.extensionRegistry !== void 0 && {
|