@formspec/build 0.1.0-alpha.15 → 0.1.0-alpha.17
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/edge-cases.d.ts +11 -0
- package/dist/__tests__/fixtures/edge-cases.d.ts.map +1 -1
- package/dist/__tests__/fixtures/example-numeric-extension.d.ts +20 -0
- package/dist/__tests__/fixtures/example-numeric-extension.d.ts.map +1 -0
- package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts +31 -0
- package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts.map +1 -0
- package/dist/__tests__/mixed-authoring.test.d.ts +2 -0
- package/dist/__tests__/mixed-authoring.test.d.ts.map +1 -0
- package/dist/__tests__/numeric-extension.integration.test.d.ts +2 -0
- package/dist/__tests__/numeric-extension.integration.test.d.ts.map +1 -0
- package/dist/__tests__/parity/utils.d.ts +5 -3
- package/dist/__tests__/parity/utils.d.ts.map +1 -1
- package/dist/analyzer/class-analyzer.d.ts +8 -5
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/analyzer/jsdoc-constraints.d.ts +3 -2
- package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
- package/dist/analyzer/tsdoc-parser.d.ts +38 -4
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
- package/dist/browser.cjs +371 -21
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +371 -21
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +67 -3
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
- package/dist/cli.cjs +1159 -150
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1159 -150
- package/dist/cli.js.map +1 -1
- package/dist/extensions/registry.d.ts +25 -1
- package/dist/extensions/registry.d.ts.map +1 -1
- package/dist/generators/class-schema.d.ts +4 -4
- package/dist/generators/class-schema.d.ts.map +1 -1
- package/dist/generators/mixed-authoring.d.ts +45 -0
- package/dist/generators/mixed-authoring.d.ts.map +1 -0
- package/dist/index.cjs +1146 -149
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1145 -149
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +1156 -149
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +1154 -147
- package/dist/internals.js.map +1 -1
- package/dist/json-schema/ir-generator.d.ts +3 -2
- package/dist/json-schema/ir-generator.d.ts.map +1 -1
- package/dist/ui-schema/ir-generator.d.ts.map +1 -1
- package/dist/validate/constraint-validator.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/__tests__/jsdoc-constraints.test.d.ts +0 -9
- package/dist/__tests__/jsdoc-constraints.test.d.ts.map +0 -1
package/dist/browser.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAsB,KAAK,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAEhG,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGrD,YAAY,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,YAAY,EAAE,+BAA+B,EAAE,MAAM,+BAA+B,CAAC;AACrF,YAAY,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAE5E,YAAY,EACV,WAAW,EACX,cAAc,EACd,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,YAAY,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,YAAY,EACV,QAAQ,EACR,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,IAAI,EACJ,UAAU,EACV,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,gBAAgB,EAChB,yBAAyB,EACzB,mBAAmB,EACnB,0BAA0B,EAC1B,UAAU,EACV,aAAa,EACb,oBAAoB,EACpB,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,cAAc,EACd,kBAAkB,EAClB,qBAAqB,EACrB,QAAQ,IAAI,cAAc,GAC3B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAGlF,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAG5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAGjF,OAAO,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAChE,YAAY,EACV,oBAAoB,EACpB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,oCAAoC,CAAC;AAE5C;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,yCAAyC;IACzC,QAAQ,CAAC,UAAU,EAAE,cAAc,CAAC;IACpC,yCAAyC;IACzC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;CAC7B;AAED;;;;;GAKG;AACH,MAAM,MAAM,uBAAuB,GAAG,yBAAyB,CAAC;AAEhE;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,SAAS,WAAW,EAAE,EAC/D,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EACjB,OAAO,CAAC,EAAE,uBAAuB,GAChC,WAAW,CAKb"}
|
package/dist/browser.js
CHANGED
|
@@ -330,6 +330,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
330
330
|
const ctx = makeContext(options);
|
|
331
331
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
332
332
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
333
|
+
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
334
|
+
applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
|
|
335
|
+
}
|
|
333
336
|
}
|
|
334
337
|
const properties = {};
|
|
335
338
|
const required = [];
|
|
@@ -341,6 +344,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
341
344
|
properties,
|
|
342
345
|
...uniqueRequired.length > 0 && { required: uniqueRequired }
|
|
343
346
|
};
|
|
347
|
+
if (ir.annotations && ir.annotations.length > 0) {
|
|
348
|
+
applyAnnotations(result, ir.annotations, ctx);
|
|
349
|
+
}
|
|
344
350
|
if (Object.keys(ctx.defs).length > 0) {
|
|
345
351
|
result.$defs = ctx.defs;
|
|
346
352
|
}
|
|
@@ -370,22 +376,51 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
370
376
|
}
|
|
371
377
|
function generateFieldSchema(field, ctx) {
|
|
372
378
|
const schema = generateTypeNode(field.type, ctx);
|
|
379
|
+
const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
|
|
373
380
|
const directConstraints = [];
|
|
381
|
+
const itemConstraints = [];
|
|
374
382
|
const pathConstraints = [];
|
|
375
383
|
for (const c of field.constraints) {
|
|
376
384
|
if (c.path) {
|
|
377
385
|
pathConstraints.push(c);
|
|
386
|
+
} else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
|
|
387
|
+
itemConstraints.push(c);
|
|
378
388
|
} else {
|
|
379
389
|
directConstraints.push(c);
|
|
380
390
|
}
|
|
381
391
|
}
|
|
382
392
|
applyConstraints(schema, directConstraints, ctx);
|
|
383
|
-
|
|
393
|
+
if (itemStringSchema !== void 0) {
|
|
394
|
+
applyConstraints(itemStringSchema, itemConstraints, ctx);
|
|
395
|
+
}
|
|
396
|
+
const rootAnnotations = [];
|
|
397
|
+
const itemAnnotations = [];
|
|
398
|
+
for (const annotation of field.annotations) {
|
|
399
|
+
if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
|
|
400
|
+
itemAnnotations.push(annotation);
|
|
401
|
+
} else {
|
|
402
|
+
rootAnnotations.push(annotation);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
applyAnnotations(schema, rootAnnotations, ctx);
|
|
406
|
+
if (itemStringSchema !== void 0) {
|
|
407
|
+
applyAnnotations(itemStringSchema, itemAnnotations, ctx);
|
|
408
|
+
}
|
|
384
409
|
if (pathConstraints.length === 0) {
|
|
385
410
|
return schema;
|
|
386
411
|
}
|
|
387
412
|
return applyPathTargetedConstraints(schema, pathConstraints, ctx);
|
|
388
413
|
}
|
|
414
|
+
function isStringItemConstraint(constraint) {
|
|
415
|
+
switch (constraint.constraintKind) {
|
|
416
|
+
case "minLength":
|
|
417
|
+
case "maxLength":
|
|
418
|
+
case "pattern":
|
|
419
|
+
return true;
|
|
420
|
+
default:
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
389
424
|
function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
390
425
|
if (schema.type === "array" && schema.items) {
|
|
391
426
|
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
|
|
@@ -603,6 +638,9 @@ function applyConstraints(schema, constraints, ctx) {
|
|
|
603
638
|
case "uniqueItems":
|
|
604
639
|
schema.uniqueItems = constraint.value;
|
|
605
640
|
break;
|
|
641
|
+
case "const":
|
|
642
|
+
schema.const = constraint.value;
|
|
643
|
+
break;
|
|
606
644
|
case "allowedMembers":
|
|
607
645
|
break;
|
|
608
646
|
case "custom":
|
|
@@ -627,8 +665,14 @@ function applyAnnotations(schema, annotations, ctx) {
|
|
|
627
665
|
case "defaultValue":
|
|
628
666
|
schema.default = annotation.value;
|
|
629
667
|
break;
|
|
668
|
+
case "format":
|
|
669
|
+
schema.format = annotation.value;
|
|
670
|
+
break;
|
|
630
671
|
case "deprecated":
|
|
631
672
|
schema.deprecated = true;
|
|
673
|
+
if (annotation.message !== void 0 && annotation.message !== "") {
|
|
674
|
+
schema["x-formspec-deprecation-description"] = annotation.message;
|
|
675
|
+
}
|
|
632
676
|
break;
|
|
633
677
|
case "placeholder":
|
|
634
678
|
break;
|
|
@@ -660,7 +704,12 @@ function applyCustomConstraint(schema, constraint, ctx) {
|
|
|
660
704
|
`Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
|
|
661
705
|
);
|
|
662
706
|
}
|
|
663
|
-
|
|
707
|
+
assignVendorPrefixedExtensionKeywords(
|
|
708
|
+
schema,
|
|
709
|
+
registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
|
|
710
|
+
ctx.vendorPrefix,
|
|
711
|
+
`custom constraint "${constraint.constraintId}"`
|
|
712
|
+
);
|
|
664
713
|
}
|
|
665
714
|
function applyCustomAnnotation(schema, annotation, ctx) {
|
|
666
715
|
const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
|
|
@@ -672,7 +721,22 @@ function applyCustomAnnotation(schema, annotation, ctx) {
|
|
|
672
721
|
if (registration.toJsonSchema === void 0) {
|
|
673
722
|
return;
|
|
674
723
|
}
|
|
675
|
-
|
|
724
|
+
assignVendorPrefixedExtensionKeywords(
|
|
725
|
+
schema,
|
|
726
|
+
registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
|
|
727
|
+
ctx.vendorPrefix,
|
|
728
|
+
`custom annotation "${annotation.annotationId}"`
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
|
|
732
|
+
for (const [key, value] of Object.entries(extensionSchema)) {
|
|
733
|
+
if (!key.startsWith(`${vendorPrefix}-`)) {
|
|
734
|
+
throw new Error(
|
|
735
|
+
`Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
schema[key] = value;
|
|
739
|
+
}
|
|
676
740
|
}
|
|
677
741
|
|
|
678
742
|
// src/json-schema/generator.ts
|
|
@@ -816,25 +880,31 @@ function createShowRule(fieldName, value) {
|
|
|
816
880
|
}
|
|
817
881
|
};
|
|
818
882
|
}
|
|
883
|
+
function flattenConditionSchema(scope, schema) {
|
|
884
|
+
if (schema.allOf === void 0) {
|
|
885
|
+
if (scope === "#") {
|
|
886
|
+
return [schema];
|
|
887
|
+
}
|
|
888
|
+
const fieldName = scope.replace("#/properties/", "");
|
|
889
|
+
return [
|
|
890
|
+
{
|
|
891
|
+
properties: {
|
|
892
|
+
[fieldName]: schema
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
];
|
|
896
|
+
}
|
|
897
|
+
return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
|
|
898
|
+
}
|
|
819
899
|
function combineRules(parentRule, childRule) {
|
|
820
|
-
const parentCondition = parentRule.condition;
|
|
821
|
-
const childCondition = childRule.condition;
|
|
822
900
|
return {
|
|
823
901
|
effect: "SHOW",
|
|
824
902
|
condition: {
|
|
825
903
|
scope: "#",
|
|
826
904
|
schema: {
|
|
827
905
|
allOf: [
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
[parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
|
|
831
|
-
}
|
|
832
|
-
},
|
|
833
|
-
{
|
|
834
|
-
properties: {
|
|
835
|
-
[childCondition.scope.replace("#/properties/", "")]: childCondition.schema
|
|
836
|
-
}
|
|
837
|
-
}
|
|
906
|
+
...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
|
|
907
|
+
...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
|
|
838
908
|
]
|
|
839
909
|
}
|
|
840
910
|
}
|
|
@@ -842,10 +912,14 @@ function combineRules(parentRule, childRule) {
|
|
|
842
912
|
}
|
|
843
913
|
function fieldNodeToControl(field, parentRule) {
|
|
844
914
|
const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
|
|
915
|
+
const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
|
|
845
916
|
const control = {
|
|
846
917
|
type: "Control",
|
|
847
918
|
scope: fieldToScope(field.name),
|
|
848
919
|
...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
|
|
920
|
+
...placeholderAnnotation !== void 0 && {
|
|
921
|
+
options: { placeholder: placeholderAnnotation.value }
|
|
922
|
+
},
|
|
849
923
|
...parentRule !== void 0 && { rule: parentRule }
|
|
850
924
|
};
|
|
851
925
|
return control;
|
|
@@ -911,7 +985,10 @@ function getSchemaExtension(schema, key) {
|
|
|
911
985
|
// src/extensions/registry.ts
|
|
912
986
|
function createExtensionRegistry(extensions) {
|
|
913
987
|
const typeMap = /* @__PURE__ */ new Map();
|
|
988
|
+
const typeNameMap = /* @__PURE__ */ new Map();
|
|
914
989
|
const constraintMap = /* @__PURE__ */ new Map();
|
|
990
|
+
const constraintTagMap = /* @__PURE__ */ new Map();
|
|
991
|
+
const builtinBroadeningMap = /* @__PURE__ */ new Map();
|
|
915
992
|
const annotationMap = /* @__PURE__ */ new Map();
|
|
916
993
|
for (const ext of extensions) {
|
|
917
994
|
if (ext.types !== void 0) {
|
|
@@ -921,6 +998,27 @@ function createExtensionRegistry(extensions) {
|
|
|
921
998
|
throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
|
|
922
999
|
}
|
|
923
1000
|
typeMap.set(qualifiedId, type);
|
|
1001
|
+
for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
|
|
1002
|
+
if (typeNameMap.has(sourceTypeName)) {
|
|
1003
|
+
throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
|
|
1004
|
+
}
|
|
1005
|
+
typeNameMap.set(sourceTypeName, {
|
|
1006
|
+
extensionId: ext.extensionId,
|
|
1007
|
+
registration: type
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
if (type.builtinConstraintBroadenings !== void 0) {
|
|
1011
|
+
for (const broadening of type.builtinConstraintBroadenings) {
|
|
1012
|
+
const key = `${qualifiedId}:${broadening.tagName}`;
|
|
1013
|
+
if (builtinBroadeningMap.has(key)) {
|
|
1014
|
+
throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
|
|
1015
|
+
}
|
|
1016
|
+
builtinBroadeningMap.set(key, {
|
|
1017
|
+
extensionId: ext.extensionId,
|
|
1018
|
+
registration: broadening
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
924
1022
|
}
|
|
925
1023
|
}
|
|
926
1024
|
if (ext.constraints !== void 0) {
|
|
@@ -932,6 +1030,17 @@ function createExtensionRegistry(extensions) {
|
|
|
932
1030
|
constraintMap.set(qualifiedId, constraint);
|
|
933
1031
|
}
|
|
934
1032
|
}
|
|
1033
|
+
if (ext.constraintTags !== void 0) {
|
|
1034
|
+
for (const tag of ext.constraintTags) {
|
|
1035
|
+
if (constraintTagMap.has(tag.tagName)) {
|
|
1036
|
+
throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
|
|
1037
|
+
}
|
|
1038
|
+
constraintTagMap.set(tag.tagName, {
|
|
1039
|
+
extensionId: ext.extensionId,
|
|
1040
|
+
registration: tag
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
935
1044
|
if (ext.annotations !== void 0) {
|
|
936
1045
|
for (const annotation of ext.annotations) {
|
|
937
1046
|
const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
|
|
@@ -945,7 +1054,10 @@ function createExtensionRegistry(extensions) {
|
|
|
945
1054
|
return {
|
|
946
1055
|
extensions,
|
|
947
1056
|
findType: (typeId) => typeMap.get(typeId),
|
|
1057
|
+
findTypeByName: (typeName) => typeNameMap.get(typeName),
|
|
948
1058
|
findConstraint: (constraintId) => constraintMap.get(constraintId),
|
|
1059
|
+
findConstraintTag: (tagName) => constraintTagMap.get(tagName),
|
|
1060
|
+
findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
|
|
949
1061
|
findAnnotation: (annotationId) => annotationMap.get(annotationId)
|
|
950
1062
|
};
|
|
951
1063
|
}
|
|
@@ -1013,6 +1125,7 @@ var jsonSchema7Schema = z3.lazy(
|
|
|
1013
1125
|
);
|
|
1014
1126
|
|
|
1015
1127
|
// src/validate/constraint-validator.ts
|
|
1128
|
+
import { normalizeConstraintTagName } from "@formspec/core";
|
|
1016
1129
|
function addContradiction(ctx, message, primary, related) {
|
|
1017
1130
|
ctx.diagnostics.push({
|
|
1018
1131
|
code: "CONTRADICTING_CONSTRAINTS",
|
|
@@ -1040,6 +1153,15 @@ function addUnknownExtension(ctx, message, primary) {
|
|
|
1040
1153
|
relatedLocations: []
|
|
1041
1154
|
});
|
|
1042
1155
|
}
|
|
1156
|
+
function addUnknownPathTarget(ctx, message, primary) {
|
|
1157
|
+
ctx.diagnostics.push({
|
|
1158
|
+
code: "UNKNOWN_PATH_TARGET",
|
|
1159
|
+
message,
|
|
1160
|
+
severity: "error",
|
|
1161
|
+
primaryLocation: primary,
|
|
1162
|
+
relatedLocations: []
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1043
1165
|
function addConstraintBroadening(ctx, message, primary, related) {
|
|
1044
1166
|
ctx.diagnostics.push({
|
|
1045
1167
|
code: "CONSTRAINT_BROADENING",
|
|
@@ -1049,6 +1171,13 @@ function addConstraintBroadening(ctx, message, primary, related) {
|
|
|
1049
1171
|
relatedLocations: [related]
|
|
1050
1172
|
});
|
|
1051
1173
|
}
|
|
1174
|
+
function getExtensionIdFromConstraintId(constraintId) {
|
|
1175
|
+
const separator = constraintId.lastIndexOf("/");
|
|
1176
|
+
if (separator <= 0) {
|
|
1177
|
+
return null;
|
|
1178
|
+
}
|
|
1179
|
+
return constraintId.slice(0, separator);
|
|
1180
|
+
}
|
|
1052
1181
|
function findNumeric(constraints, constraintKind) {
|
|
1053
1182
|
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
1054
1183
|
}
|
|
@@ -1060,6 +1189,45 @@ function findAllowedMembers(constraints) {
|
|
|
1060
1189
|
(c) => c.constraintKind === "allowedMembers"
|
|
1061
1190
|
);
|
|
1062
1191
|
}
|
|
1192
|
+
function findConstConstraints(constraints) {
|
|
1193
|
+
return constraints.filter(
|
|
1194
|
+
(c) => c.constraintKind === "const"
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
function jsonValueEquals(left, right) {
|
|
1198
|
+
if (left === right) {
|
|
1199
|
+
return true;
|
|
1200
|
+
}
|
|
1201
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
1202
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
|
1203
|
+
return false;
|
|
1204
|
+
}
|
|
1205
|
+
return left.every((item, index) => jsonValueEquals(item, right[index]));
|
|
1206
|
+
}
|
|
1207
|
+
if (isJsonObject(left) || isJsonObject(right)) {
|
|
1208
|
+
if (!isJsonObject(left) || !isJsonObject(right)) {
|
|
1209
|
+
return false;
|
|
1210
|
+
}
|
|
1211
|
+
const leftKeys = Object.keys(left).sort();
|
|
1212
|
+
const rightKeys = Object.keys(right).sort();
|
|
1213
|
+
if (leftKeys.length !== rightKeys.length) {
|
|
1214
|
+
return false;
|
|
1215
|
+
}
|
|
1216
|
+
return leftKeys.every((key, index) => {
|
|
1217
|
+
const rightKey = rightKeys[index];
|
|
1218
|
+
if (rightKey !== key) {
|
|
1219
|
+
return false;
|
|
1220
|
+
}
|
|
1221
|
+
const leftValue = left[key];
|
|
1222
|
+
const rightValue = right[rightKey];
|
|
1223
|
+
return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
return false;
|
|
1227
|
+
}
|
|
1228
|
+
function isJsonObject(value) {
|
|
1229
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1230
|
+
}
|
|
1063
1231
|
function isOrderedBoundConstraint(constraint) {
|
|
1064
1232
|
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";
|
|
1065
1233
|
}
|
|
@@ -1180,6 +1348,112 @@ function checkConstraintBroadening(ctx, fieldName, constraints) {
|
|
|
1180
1348
|
strongestByKey.set(key, constraint);
|
|
1181
1349
|
}
|
|
1182
1350
|
}
|
|
1351
|
+
function compareCustomConstraintStrength(current, previous) {
|
|
1352
|
+
const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
|
|
1353
|
+
const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
|
|
1354
|
+
switch (current.role.bound) {
|
|
1355
|
+
case "lower":
|
|
1356
|
+
return equalPayloadTiebreaker;
|
|
1357
|
+
case "upper":
|
|
1358
|
+
return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
|
|
1359
|
+
case "exact":
|
|
1360
|
+
return order === 0 ? 0 : Number.NaN;
|
|
1361
|
+
default: {
|
|
1362
|
+
const _exhaustive = current.role.bound;
|
|
1363
|
+
return _exhaustive;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
function compareSemanticInclusivity(currentInclusive, previousInclusive) {
|
|
1368
|
+
if (currentInclusive === previousInclusive) {
|
|
1369
|
+
return 0;
|
|
1370
|
+
}
|
|
1371
|
+
return currentInclusive ? -1 : 1;
|
|
1372
|
+
}
|
|
1373
|
+
function customConstraintsContradict(lower, upper) {
|
|
1374
|
+
const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
|
|
1375
|
+
if (order > 0) {
|
|
1376
|
+
return true;
|
|
1377
|
+
}
|
|
1378
|
+
if (order < 0) {
|
|
1379
|
+
return false;
|
|
1380
|
+
}
|
|
1381
|
+
return !lower.role.inclusive || !upper.role.inclusive;
|
|
1382
|
+
}
|
|
1383
|
+
function describeCustomConstraintTag(constraint) {
|
|
1384
|
+
return constraint.provenance.tagName ?? constraint.constraintId;
|
|
1385
|
+
}
|
|
1386
|
+
function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
|
|
1387
|
+
if (ctx.extensionRegistry === void 0) {
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
1391
|
+
const lowerByFamily = /* @__PURE__ */ new Map();
|
|
1392
|
+
const upperByFamily = /* @__PURE__ */ new Map();
|
|
1393
|
+
for (const constraint of constraints) {
|
|
1394
|
+
if (constraint.constraintKind !== "custom") {
|
|
1395
|
+
continue;
|
|
1396
|
+
}
|
|
1397
|
+
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
1398
|
+
if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
|
|
1399
|
+
continue;
|
|
1400
|
+
}
|
|
1401
|
+
const entry = {
|
|
1402
|
+
constraint,
|
|
1403
|
+
comparePayloads: registration.comparePayloads,
|
|
1404
|
+
role: registration.semanticRole
|
|
1405
|
+
};
|
|
1406
|
+
const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
|
|
1407
|
+
const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
|
|
1408
|
+
const previous = strongestByKey.get(boundKey);
|
|
1409
|
+
if (previous !== void 0) {
|
|
1410
|
+
const strength = compareCustomConstraintStrength(entry, previous);
|
|
1411
|
+
if (Number.isNaN(strength)) {
|
|
1412
|
+
addContradiction(
|
|
1413
|
+
ctx,
|
|
1414
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
|
|
1415
|
+
constraint.provenance,
|
|
1416
|
+
previous.constraint.provenance
|
|
1417
|
+
);
|
|
1418
|
+
continue;
|
|
1419
|
+
}
|
|
1420
|
+
if (strength < 0) {
|
|
1421
|
+
addConstraintBroadening(
|
|
1422
|
+
ctx,
|
|
1423
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
|
|
1424
|
+
constraint.provenance,
|
|
1425
|
+
previous.constraint.provenance
|
|
1426
|
+
);
|
|
1427
|
+
continue;
|
|
1428
|
+
}
|
|
1429
|
+
if (strength > 0) {
|
|
1430
|
+
strongestByKey.set(boundKey, entry);
|
|
1431
|
+
}
|
|
1432
|
+
} else {
|
|
1433
|
+
strongestByKey.set(boundKey, entry);
|
|
1434
|
+
}
|
|
1435
|
+
if (registration.semanticRole.bound === "lower") {
|
|
1436
|
+
lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
1437
|
+
} else if (registration.semanticRole.bound === "upper") {
|
|
1438
|
+
upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
for (const [familyKey, lower] of lowerByFamily) {
|
|
1442
|
+
const upper = upperByFamily.get(familyKey);
|
|
1443
|
+
if (upper === void 0) {
|
|
1444
|
+
continue;
|
|
1445
|
+
}
|
|
1446
|
+
if (!customConstraintsContradict(lower, upper)) {
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
addContradiction(
|
|
1450
|
+
ctx,
|
|
1451
|
+
`Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
|
|
1452
|
+
lower.constraint.provenance,
|
|
1453
|
+
upper.constraint.provenance
|
|
1454
|
+
);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1183
1457
|
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
1184
1458
|
const min = findNumeric(constraints, "minimum");
|
|
1185
1459
|
const max = findNumeric(constraints, "maximum");
|
|
@@ -1266,6 +1540,25 @@ function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
|
|
|
1266
1540
|
}
|
|
1267
1541
|
}
|
|
1268
1542
|
}
|
|
1543
|
+
function checkConstContradictions(ctx, fieldName, constraints) {
|
|
1544
|
+
const constConstraints = findConstConstraints(constraints);
|
|
1545
|
+
if (constConstraints.length < 2) return;
|
|
1546
|
+
const first = constConstraints[0];
|
|
1547
|
+
if (first === void 0) return;
|
|
1548
|
+
for (let i = 1; i < constConstraints.length; i++) {
|
|
1549
|
+
const current = constConstraints[i];
|
|
1550
|
+
if (current === void 0) continue;
|
|
1551
|
+
if (jsonValueEquals(first.value, current.value)) {
|
|
1552
|
+
continue;
|
|
1553
|
+
}
|
|
1554
|
+
addContradiction(
|
|
1555
|
+
ctx,
|
|
1556
|
+
`Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
|
|
1557
|
+
first.provenance,
|
|
1558
|
+
current.provenance
|
|
1559
|
+
);
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1269
1562
|
function typeLabel(type) {
|
|
1270
1563
|
switch (type.kind) {
|
|
1271
1564
|
case "primitive":
|
|
@@ -1338,6 +1631,8 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
|
1338
1631
|
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
1339
1632
|
const isArray = effectiveType.kind === "array";
|
|
1340
1633
|
const isEnum = effectiveType.kind === "enum";
|
|
1634
|
+
const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
|
|
1635
|
+
const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
|
|
1341
1636
|
const label = typeLabel(effectiveType);
|
|
1342
1637
|
const ck = constraint.constraintKind;
|
|
1343
1638
|
switch (ck) {
|
|
@@ -1358,10 +1653,10 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
|
1358
1653
|
case "minLength":
|
|
1359
1654
|
case "maxLength":
|
|
1360
1655
|
case "pattern": {
|
|
1361
|
-
if (!isString) {
|
|
1656
|
+
if (!isString && !isStringArray) {
|
|
1362
1657
|
addTypeMismatch(
|
|
1363
1658
|
ctx,
|
|
1364
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on string fields, but field type is "${label}"`,
|
|
1659
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
|
|
1365
1660
|
constraint.provenance
|
|
1366
1661
|
);
|
|
1367
1662
|
}
|
|
@@ -1389,6 +1684,37 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
|
1389
1684
|
}
|
|
1390
1685
|
break;
|
|
1391
1686
|
}
|
|
1687
|
+
case "const": {
|
|
1688
|
+
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "boolean", "null"].includes(effectiveType.primitiveKind) || effectiveType.kind === "enum";
|
|
1689
|
+
if (!isPrimitiveConstType) {
|
|
1690
|
+
addTypeMismatch(
|
|
1691
|
+
ctx,
|
|
1692
|
+
`Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
|
|
1693
|
+
constraint.provenance
|
|
1694
|
+
);
|
|
1695
|
+
break;
|
|
1696
|
+
}
|
|
1697
|
+
if (effectiveType.kind === "primitive") {
|
|
1698
|
+
const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
|
|
1699
|
+
if (valueType !== effectiveType.primitiveKind) {
|
|
1700
|
+
addTypeMismatch(
|
|
1701
|
+
ctx,
|
|
1702
|
+
`Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
|
|
1703
|
+
constraint.provenance
|
|
1704
|
+
);
|
|
1705
|
+
}
|
|
1706
|
+
break;
|
|
1707
|
+
}
|
|
1708
|
+
const memberValues = effectiveType.members.map((member) => member.value);
|
|
1709
|
+
if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
|
|
1710
|
+
addTypeMismatch(
|
|
1711
|
+
ctx,
|
|
1712
|
+
`Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
|
|
1713
|
+
constraint.provenance
|
|
1714
|
+
);
|
|
1715
|
+
}
|
|
1716
|
+
break;
|
|
1717
|
+
}
|
|
1392
1718
|
case "custom": {
|
|
1393
1719
|
checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
|
|
1394
1720
|
break;
|
|
@@ -1407,9 +1733,9 @@ function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
|
1407
1733
|
const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
|
|
1408
1734
|
const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
|
|
1409
1735
|
if (resolution.kind === "missing-property") {
|
|
1410
|
-
|
|
1736
|
+
addUnknownPathTarget(
|
|
1411
1737
|
ctx,
|
|
1412
|
-
`Field "${
|
|
1738
|
+
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
|
|
1413
1739
|
constraint.provenance
|
|
1414
1740
|
);
|
|
1415
1741
|
continue;
|
|
@@ -1439,8 +1765,30 @@ function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
|
1439
1765
|
);
|
|
1440
1766
|
return;
|
|
1441
1767
|
}
|
|
1442
|
-
|
|
1443
|
-
if (
|
|
1768
|
+
const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : normalizeConstraintTagName(constraint.provenance.tagName.replace(/^@/, ""));
|
|
1769
|
+
if (normalizedTagName !== void 0) {
|
|
1770
|
+
const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
|
|
1771
|
+
const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
|
|
1772
|
+
if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && tagRegistration.registration.isApplicableToType?.(type) === false) {
|
|
1773
|
+
addTypeMismatch(
|
|
1774
|
+
ctx,
|
|
1775
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
1776
|
+
constraint.provenance
|
|
1777
|
+
);
|
|
1778
|
+
return;
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
if (registration.applicableTypes === null) {
|
|
1782
|
+
if (registration.isApplicableToType?.(type) === false) {
|
|
1783
|
+
addTypeMismatch(
|
|
1784
|
+
ctx,
|
|
1785
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
1786
|
+
constraint.provenance
|
|
1787
|
+
);
|
|
1788
|
+
}
|
|
1789
|
+
return;
|
|
1790
|
+
}
|
|
1791
|
+
if (!registration.applicableTypes.includes(type.kind) || registration.isApplicableToType?.(type) === false) {
|
|
1444
1792
|
addTypeMismatch(
|
|
1445
1793
|
ctx,
|
|
1446
1794
|
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
@@ -1469,7 +1817,9 @@ function validateConstraints(ctx, name, type, constraints) {
|
|
|
1469
1817
|
checkNumericContradictions(ctx, name, constraints);
|
|
1470
1818
|
checkLengthContradictions(ctx, name, constraints);
|
|
1471
1819
|
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
1820
|
+
checkConstContradictions(ctx, name, constraints);
|
|
1472
1821
|
checkConstraintBroadening(ctx, name, constraints);
|
|
1822
|
+
checkCustomConstraintSemantics(ctx, name, constraints);
|
|
1473
1823
|
checkTypeApplicability(ctx, name, type, constraints);
|
|
1474
1824
|
}
|
|
1475
1825
|
function validateElement(ctx, element) {
|