@formspec/build 0.1.0-alpha.10 → 0.1.0-alpha.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -15
- package/dist/__tests__/chain-dsl-canonicalizer.test.d.ts +2 -0
- package/dist/__tests__/chain-dsl-canonicalizer.test.d.ts.map +1 -0
- package/dist/__tests__/constraint-validator.test.d.ts +2 -0
- package/dist/__tests__/constraint-validator.test.d.ts.map +1 -0
- package/dist/__tests__/extension-api.test.d.ts +2 -0
- package/dist/__tests__/extension-api.test.d.ts.map +1 -0
- package/dist/__tests__/fixtures/example-a-builtins.d.ts +18 -0
- package/dist/__tests__/fixtures/example-a-builtins.d.ts.map +1 -1
- package/dist/__tests__/ir-analyzer.test.d.ts +11 -0
- package/dist/__tests__/ir-analyzer.test.d.ts.map +1 -0
- package/dist/__tests__/ir-jsdoc-constraints.test.d.ts +12 -0
- package/dist/__tests__/ir-jsdoc-constraints.test.d.ts.map +1 -0
- package/dist/__tests__/ir-json-schema-generator.test.d.ts +11 -0
- package/dist/__tests__/ir-json-schema-generator.test.d.ts.map +1 -0
- package/dist/__tests__/ir-ui-schema-generator.test.d.ts +2 -0
- package/dist/__tests__/ir-ui-schema-generator.test.d.ts.map +1 -0
- package/dist/__tests__/jsdoc-constraints.test.d.ts +4 -4
- package/dist/__tests__/parity/fixtures/address/chain-dsl.d.ts +9 -0
- package/dist/__tests__/parity/fixtures/address/chain-dsl.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/address/expected-ir.d.ts +9 -0
- package/dist/__tests__/parity/fixtures/address/expected-ir.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/address/tsdoc.d.ts +19 -0
- package/dist/__tests__/parity/fixtures/address/tsdoc.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/product-config/chain-dsl.d.ts +13 -0
- package/dist/__tests__/parity/fixtures/product-config/chain-dsl.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/product-config/expected-ir.d.ts +9 -0
- package/dist/__tests__/parity/fixtures/product-config/expected-ir.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/product-config/tsdoc.d.ts +28 -0
- package/dist/__tests__/parity/fixtures/product-config/tsdoc.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/user-registration/chain-dsl.d.ts +12 -0
- package/dist/__tests__/parity/fixtures/user-registration/chain-dsl.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/user-registration/expected-ir.d.ts +9 -0
- package/dist/__tests__/parity/fixtures/user-registration/expected-ir.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/user-registration/tsdoc.d.ts +19 -0
- package/dist/__tests__/parity/fixtures/user-registration/tsdoc.d.ts.map +1 -0
- package/dist/__tests__/parity/parity.test.d.ts +14 -0
- package/dist/__tests__/parity/parity.test.d.ts.map +1 -0
- package/dist/__tests__/parity/utils.d.ts +139 -0
- package/dist/__tests__/parity/utils.d.ts.map +1 -0
- package/dist/analyzer/class-analyzer.d.ts +54 -99
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/analyzer/jsdoc-constraints.d.ts +78 -30
- package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
- package/dist/analyzer/tsdoc-parser.d.ts +61 -0
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -0
- package/dist/browser.cjs +1200 -0
- package/dist/browser.cjs.map +1 -0
- package/dist/browser.d.ts +12 -6
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +1147 -44
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +385 -160
- package/dist/canonicalize/chain-dsl-canonicalizer.d.ts +18 -0
- package/dist/canonicalize/chain-dsl-canonicalizer.d.ts.map +1 -0
- package/dist/canonicalize/index.d.ts +8 -0
- package/dist/canonicalize/index.d.ts.map +1 -0
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts +34 -0
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -0
- package/dist/cli.cjs +2028 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.js +1978 -101
- package/dist/cli.js.map +1 -1
- package/dist/extensions/index.d.ts +8 -0
- package/dist/extensions/index.d.ts.map +1 -0
- package/dist/extensions/registry.d.ts +55 -0
- package/dist/extensions/registry.d.ts.map +1 -0
- package/dist/generators/class-schema.d.ts +28 -47
- package/dist/generators/class-schema.d.ts.map +1 -1
- package/dist/generators/method-schema.d.ts +6 -8
- package/dist/generators/method-schema.d.ts.map +1 -1
- package/dist/index.cjs +1832 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +8 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1779 -114
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +2125 -0
- package/dist/internals.cjs.map +1 -0
- package/dist/internals.d.ts +12 -2
- package/dist/internals.d.ts.map +1 -1
- package/dist/internals.js +2084 -21
- package/dist/internals.js.map +1 -1
- package/dist/json-schema/generator.d.ts +10 -5
- package/dist/json-schema/generator.d.ts.map +1 -1
- package/dist/json-schema/ir-generator.d.ts +84 -0
- package/dist/json-schema/ir-generator.d.ts.map +1 -0
- package/dist/json-schema/schema.d.ts +16 -0
- package/dist/json-schema/schema.d.ts.map +1 -0
- package/dist/json-schema/types.d.ts +5 -6
- package/dist/json-schema/types.d.ts.map +1 -1
- package/dist/ui-schema/generator.d.ts +5 -0
- package/dist/ui-schema/generator.d.ts.map +1 -1
- package/dist/ui-schema/ir-generator.d.ts +53 -0
- package/dist/ui-schema/ir-generator.d.ts.map +1 -0
- package/dist/ui-schema/schema.d.ts +357 -0
- package/dist/ui-schema/schema.d.ts.map +1 -0
- package/dist/ui-schema/types.d.ts +8 -73
- package/dist/ui-schema/types.d.ts.map +1 -1
- package/dist/validate/constraint-validator.d.ts +66 -0
- package/dist/validate/constraint-validator.d.ts.map +1 -0
- package/dist/validate/index.d.ts +9 -0
- package/dist/validate/index.d.ts.map +1 -0
- package/package.json +15 -9
- package/dist/__tests__/analyzer-edge-cases.test.d.ts +0 -13
- package/dist/__tests__/analyzer-edge-cases.test.d.ts.map +0 -1
- package/dist/__tests__/analyzer-edge-cases.test.js +0 -376
- package/dist/__tests__/analyzer-edge-cases.test.js.map +0 -1
- package/dist/__tests__/analyzer.test.d.ts +0 -5
- package/dist/__tests__/analyzer.test.d.ts.map +0 -1
- package/dist/__tests__/analyzer.test.js +0 -190
- package/dist/__tests__/analyzer.test.js.map +0 -1
- package/dist/__tests__/cli.test.js +0 -178
- package/dist/__tests__/cli.test.js.map +0 -1
- package/dist/__tests__/codegen.test.d.ts +0 -5
- package/dist/__tests__/codegen.test.d.ts.map +0 -1
- package/dist/__tests__/codegen.test.js +0 -506
- package/dist/__tests__/codegen.test.js.map +0 -1
- package/dist/__tests__/decorator-pipeline.test.d.ts +0 -11
- package/dist/__tests__/decorator-pipeline.test.d.ts.map +0 -1
- package/dist/__tests__/decorator-pipeline.test.js +0 -460
- package/dist/__tests__/decorator-pipeline.test.js.map +0 -1
- package/dist/__tests__/edge-cases.test.js +0 -215
- package/dist/__tests__/edge-cases.test.js.map +0 -1
- package/dist/__tests__/fixtures/edge-cases.js +0 -137
- package/dist/__tests__/fixtures/edge-cases.js.map +0 -1
- package/dist/__tests__/fixtures/example-a-builtins.js +0 -100
- package/dist/__tests__/fixtures/example-a-builtins.js.map +0 -1
- package/dist/__tests__/fixtures/example-b-decorators.d.ts +0 -5
- package/dist/__tests__/fixtures/example-b-decorators.d.ts.map +0 -1
- package/dist/__tests__/fixtures/example-b-decorators.js +0 -5
- package/dist/__tests__/fixtures/example-b-decorators.js.map +0 -1
- package/dist/__tests__/fixtures/example-b-extended.d.ts +0 -5
- package/dist/__tests__/fixtures/example-b-extended.d.ts.map +0 -1
- package/dist/__tests__/fixtures/example-b-extended.js +0 -60
- package/dist/__tests__/fixtures/example-b-extended.js.map +0 -1
- package/dist/__tests__/fixtures/example-c-custom.d.ts +0 -5
- package/dist/__tests__/fixtures/example-c-custom.d.ts.map +0 -1
- package/dist/__tests__/fixtures/example-c-custom.js +0 -61
- package/dist/__tests__/fixtures/example-c-custom.js.map +0 -1
- package/dist/__tests__/fixtures/example-c-decorators.d.ts +0 -5
- package/dist/__tests__/fixtures/example-c-decorators.d.ts.map +0 -1
- package/dist/__tests__/fixtures/example-c-decorators.js +0 -4
- package/dist/__tests__/fixtures/example-c-decorators.js.map +0 -1
- package/dist/__tests__/fixtures/example-d-mixed-decorators.d.ts +0 -6
- package/dist/__tests__/fixtures/example-d-mixed-decorators.d.ts.map +0 -1
- package/dist/__tests__/fixtures/example-d-mixed-decorators.js +0 -75
- package/dist/__tests__/fixtures/example-d-mixed-decorators.js.map +0 -1
- package/dist/__tests__/fixtures/example-e-decorators.d.ts +0 -11
- package/dist/__tests__/fixtures/example-e-decorators.d.ts.map +0 -1
- package/dist/__tests__/fixtures/example-e-decorators.js +0 -10
- package/dist/__tests__/fixtures/example-e-decorators.js.map +0 -1
- package/dist/__tests__/fixtures/example-e-no-namespace.d.ts +0 -5
- package/dist/__tests__/fixtures/example-e-no-namespace.d.ts.map +0 -1
- package/dist/__tests__/fixtures/example-e-no-namespace.js +0 -61
- package/dist/__tests__/fixtures/example-e-no-namespace.js.map +0 -1
- package/dist/__tests__/fixtures/example-interface-types.js +0 -8
- package/dist/__tests__/fixtures/example-interface-types.js.map +0 -1
- package/dist/__tests__/fixtures/example-jsdoc-constraints.d.ts +0 -16
- package/dist/__tests__/fixtures/example-jsdoc-constraints.d.ts.map +0 -1
- package/dist/__tests__/fixtures/example-jsdoc-constraints.js +0 -98
- package/dist/__tests__/fixtures/example-jsdoc-constraints.js.map +0 -1
- package/dist/__tests__/fixtures/example-nested-class.d.ts +0 -45
- package/dist/__tests__/fixtures/example-nested-class.d.ts.map +0 -1
- package/dist/__tests__/fixtures/example-nested-class.js +0 -248
- package/dist/__tests__/fixtures/example-nested-class.js.map +0 -1
- package/dist/__tests__/fixtures/sample-forms.js +0 -78
- package/dist/__tests__/fixtures/sample-forms.js.map +0 -1
- package/dist/__tests__/generator.test.js +0 -234
- package/dist/__tests__/generator.test.js.map +0 -1
- package/dist/__tests__/integration.test.js +0 -161
- package/dist/__tests__/integration.test.js.map +0 -1
- package/dist/__tests__/interface-types.test.d.ts +0 -11
- package/dist/__tests__/interface-types.test.d.ts.map +0 -1
- package/dist/__tests__/interface-types.test.js +0 -404
- package/dist/__tests__/interface-types.test.js.map +0 -1
- package/dist/__tests__/jsdoc-constraints.test.js +0 -465
- package/dist/__tests__/jsdoc-constraints.test.js.map +0 -1
- package/dist/__tests__/write-schemas.test.js +0 -198
- package/dist/__tests__/write-schemas.test.js.map +0 -1
- package/dist/analyzer/class-analyzer.js +0 -377
- package/dist/analyzer/class-analyzer.js.map +0 -1
- package/dist/analyzer/decorator-extractor.d.ts +0 -78
- package/dist/analyzer/decorator-extractor.d.ts.map +0 -1
- package/dist/analyzer/decorator-extractor.js +0 -336
- package/dist/analyzer/decorator-extractor.js.map +0 -1
- package/dist/analyzer/jsdoc-constraints.js +0 -153
- package/dist/analyzer/jsdoc-constraints.js.map +0 -1
- package/dist/analyzer/program.js +0 -114
- package/dist/analyzer/program.js.map +0 -1
- package/dist/analyzer/type-converter.d.ts +0 -75
- package/dist/analyzer/type-converter.d.ts.map +0 -1
- package/dist/analyzer/type-converter.js +0 -474
- package/dist/analyzer/type-converter.js.map +0 -1
- package/dist/codegen/index.d.ts +0 -75
- package/dist/codegen/index.d.ts.map +0 -1
- package/dist/codegen/index.js +0 -597
- package/dist/codegen/index.js.map +0 -1
- package/dist/generators/class-schema.js +0 -140
- package/dist/generators/class-schema.js.map +0 -1
- package/dist/generators/method-schema.js +0 -108
- package/dist/generators/method-schema.js.map +0 -1
- package/dist/json-schema/generator.js +0 -166
- package/dist/json-schema/generator.js.map +0 -1
- package/dist/json-schema/types.js +0 -33
- package/dist/json-schema/types.js.map +0 -1
- package/dist/ui-schema/generator.js +0 -148
- package/dist/ui-schema/generator.js.map +0 -1
- package/dist/ui-schema/types.js +0 -8
- package/dist/ui-schema/types.js.map +0 -1
|
@@ -0,0 +1,2125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/internals.ts
|
|
31
|
+
var internals_exports = {};
|
|
32
|
+
__export(internals_exports, {
|
|
33
|
+
analyzeClassToIR: () => analyzeClassToIR,
|
|
34
|
+
analyzeInterfaceToIR: () => analyzeInterfaceToIR,
|
|
35
|
+
analyzeTypeAliasToIR: () => analyzeTypeAliasToIR,
|
|
36
|
+
canonicalizeChainDSL: () => canonicalizeChainDSL,
|
|
37
|
+
canonicalizeTSDoc: () => canonicalizeTSDoc,
|
|
38
|
+
collectFormSpecReferences: () => collectFormSpecReferences,
|
|
39
|
+
createExtensionRegistry: () => createExtensionRegistry,
|
|
40
|
+
createProgramContext: () => createProgramContext,
|
|
41
|
+
findClassByName: () => findClassByName,
|
|
42
|
+
findInterfaceByName: () => findInterfaceByName,
|
|
43
|
+
findTypeAliasByName: () => findTypeAliasByName,
|
|
44
|
+
generateClassSchemas: () => generateClassSchemas,
|
|
45
|
+
generateJsonSchemaFromIR: () => generateJsonSchemaFromIR,
|
|
46
|
+
generateMethodSchemas: () => generateMethodSchemas,
|
|
47
|
+
generateUiSchemaFromIR: () => generateUiSchemaFromIR,
|
|
48
|
+
validateIR: () => validateIR
|
|
49
|
+
});
|
|
50
|
+
module.exports = __toCommonJS(internals_exports);
|
|
51
|
+
|
|
52
|
+
// src/canonicalize/chain-dsl-canonicalizer.ts
|
|
53
|
+
var import_core = require("@formspec/core");
|
|
54
|
+
var CHAIN_DSL_PROVENANCE = {
|
|
55
|
+
surface: "chain-dsl",
|
|
56
|
+
file: "",
|
|
57
|
+
line: 0,
|
|
58
|
+
column: 0
|
|
59
|
+
};
|
|
60
|
+
function isGroup(el) {
|
|
61
|
+
return el._type === "group";
|
|
62
|
+
}
|
|
63
|
+
function isConditional(el) {
|
|
64
|
+
return el._type === "conditional";
|
|
65
|
+
}
|
|
66
|
+
function isField(el) {
|
|
67
|
+
return el._type === "field";
|
|
68
|
+
}
|
|
69
|
+
function canonicalizeChainDSL(form) {
|
|
70
|
+
return {
|
|
71
|
+
kind: "form-ir",
|
|
72
|
+
irVersion: import_core.IR_VERSION,
|
|
73
|
+
elements: canonicalizeElements(form.elements),
|
|
74
|
+
typeRegistry: {},
|
|
75
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function canonicalizeElements(elements) {
|
|
79
|
+
return elements.map(canonicalizeElement);
|
|
80
|
+
}
|
|
81
|
+
function canonicalizeElement(element) {
|
|
82
|
+
if (isField(element)) {
|
|
83
|
+
return canonicalizeField(element);
|
|
84
|
+
}
|
|
85
|
+
if (isGroup(element)) {
|
|
86
|
+
return canonicalizeGroup(element);
|
|
87
|
+
}
|
|
88
|
+
if (isConditional(element)) {
|
|
89
|
+
return canonicalizeConditional(element);
|
|
90
|
+
}
|
|
91
|
+
const _exhaustive = element;
|
|
92
|
+
throw new Error(`Unknown element type: ${JSON.stringify(_exhaustive)}`);
|
|
93
|
+
}
|
|
94
|
+
function canonicalizeField(field) {
|
|
95
|
+
switch (field._field) {
|
|
96
|
+
case "text":
|
|
97
|
+
return canonicalizeTextField(field);
|
|
98
|
+
case "number":
|
|
99
|
+
return canonicalizeNumberField(field);
|
|
100
|
+
case "boolean":
|
|
101
|
+
return canonicalizeBooleanField(field);
|
|
102
|
+
case "enum":
|
|
103
|
+
return canonicalizeStaticEnumField(field);
|
|
104
|
+
case "dynamic_enum":
|
|
105
|
+
return canonicalizeDynamicEnumField(field);
|
|
106
|
+
case "dynamic_schema":
|
|
107
|
+
return canonicalizeDynamicSchemaField(field);
|
|
108
|
+
case "array":
|
|
109
|
+
return canonicalizeArrayField(field);
|
|
110
|
+
case "object":
|
|
111
|
+
return canonicalizeObjectField(field);
|
|
112
|
+
default: {
|
|
113
|
+
const _exhaustive = field;
|
|
114
|
+
throw new Error(`Unknown field type: ${JSON.stringify(_exhaustive)}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function canonicalizeTextField(field) {
|
|
119
|
+
const type = { kind: "primitive", primitiveKind: "string" };
|
|
120
|
+
return buildFieldNode(
|
|
121
|
+
field.name,
|
|
122
|
+
type,
|
|
123
|
+
field.required,
|
|
124
|
+
buildAnnotations(field.label, field.placeholder)
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
function canonicalizeNumberField(field) {
|
|
128
|
+
const type = { kind: "primitive", primitiveKind: "number" };
|
|
129
|
+
const constraints = [];
|
|
130
|
+
if (field.min !== void 0) {
|
|
131
|
+
const c = {
|
|
132
|
+
kind: "constraint",
|
|
133
|
+
constraintKind: "minimum",
|
|
134
|
+
value: field.min,
|
|
135
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
136
|
+
};
|
|
137
|
+
constraints.push(c);
|
|
138
|
+
}
|
|
139
|
+
if (field.max !== void 0) {
|
|
140
|
+
const c = {
|
|
141
|
+
kind: "constraint",
|
|
142
|
+
constraintKind: "maximum",
|
|
143
|
+
value: field.max,
|
|
144
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
145
|
+
};
|
|
146
|
+
constraints.push(c);
|
|
147
|
+
}
|
|
148
|
+
return buildFieldNode(
|
|
149
|
+
field.name,
|
|
150
|
+
type,
|
|
151
|
+
field.required,
|
|
152
|
+
buildAnnotations(field.label),
|
|
153
|
+
constraints
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
function canonicalizeBooleanField(field) {
|
|
157
|
+
const type = { kind: "primitive", primitiveKind: "boolean" };
|
|
158
|
+
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
159
|
+
}
|
|
160
|
+
function canonicalizeStaticEnumField(field) {
|
|
161
|
+
const members = field.options.map((opt) => {
|
|
162
|
+
if (typeof opt === "string") {
|
|
163
|
+
return { value: opt };
|
|
164
|
+
}
|
|
165
|
+
return { value: opt.id, displayName: opt.label };
|
|
166
|
+
});
|
|
167
|
+
const type = { kind: "enum", members };
|
|
168
|
+
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
169
|
+
}
|
|
170
|
+
function canonicalizeDynamicEnumField(field) {
|
|
171
|
+
const type = {
|
|
172
|
+
kind: "dynamic",
|
|
173
|
+
dynamicKind: "enum",
|
|
174
|
+
sourceKey: field.source,
|
|
175
|
+
parameterFields: field.params ? [...field.params] : []
|
|
176
|
+
};
|
|
177
|
+
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
178
|
+
}
|
|
179
|
+
function canonicalizeDynamicSchemaField(field) {
|
|
180
|
+
const type = {
|
|
181
|
+
kind: "dynamic",
|
|
182
|
+
dynamicKind: "schema",
|
|
183
|
+
sourceKey: field.schemaSource,
|
|
184
|
+
parameterFields: []
|
|
185
|
+
};
|
|
186
|
+
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
187
|
+
}
|
|
188
|
+
function canonicalizeArrayField(field) {
|
|
189
|
+
const itemProperties = buildObjectProperties(field.items);
|
|
190
|
+
const itemsType = {
|
|
191
|
+
kind: "object",
|
|
192
|
+
properties: itemProperties,
|
|
193
|
+
additionalProperties: false
|
|
194
|
+
};
|
|
195
|
+
const type = { kind: "array", items: itemsType };
|
|
196
|
+
const constraints = [];
|
|
197
|
+
if (field.minItems !== void 0) {
|
|
198
|
+
const c = {
|
|
199
|
+
kind: "constraint",
|
|
200
|
+
constraintKind: "minItems",
|
|
201
|
+
value: field.minItems,
|
|
202
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
203
|
+
};
|
|
204
|
+
constraints.push(c);
|
|
205
|
+
}
|
|
206
|
+
if (field.maxItems !== void 0) {
|
|
207
|
+
const c = {
|
|
208
|
+
kind: "constraint",
|
|
209
|
+
constraintKind: "maxItems",
|
|
210
|
+
value: field.maxItems,
|
|
211
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
212
|
+
};
|
|
213
|
+
constraints.push(c);
|
|
214
|
+
}
|
|
215
|
+
return buildFieldNode(
|
|
216
|
+
field.name,
|
|
217
|
+
type,
|
|
218
|
+
field.required,
|
|
219
|
+
buildAnnotations(field.label),
|
|
220
|
+
constraints
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
function canonicalizeObjectField(field) {
|
|
224
|
+
const properties = buildObjectProperties(field.properties);
|
|
225
|
+
const type = {
|
|
226
|
+
kind: "object",
|
|
227
|
+
properties,
|
|
228
|
+
additionalProperties: false
|
|
229
|
+
};
|
|
230
|
+
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
231
|
+
}
|
|
232
|
+
function canonicalizeGroup(g) {
|
|
233
|
+
return {
|
|
234
|
+
kind: "group",
|
|
235
|
+
label: g.label,
|
|
236
|
+
elements: canonicalizeElements(g.elements),
|
|
237
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function canonicalizeConditional(c) {
|
|
241
|
+
return {
|
|
242
|
+
kind: "conditional",
|
|
243
|
+
fieldName: c.field,
|
|
244
|
+
// Conditional values from the chain DSL are JSON-serializable primitives
|
|
245
|
+
// (strings, numbers, booleans) produced by the `is()` predicate helper.
|
|
246
|
+
value: assertJsonValue(c.value),
|
|
247
|
+
elements: canonicalizeElements(c.elements),
|
|
248
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function assertJsonValue(v) {
|
|
252
|
+
if (v === null || typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
253
|
+
return v;
|
|
254
|
+
}
|
|
255
|
+
if (Array.isArray(v)) {
|
|
256
|
+
return v.map(assertJsonValue);
|
|
257
|
+
}
|
|
258
|
+
if (typeof v === "object") {
|
|
259
|
+
const result = {};
|
|
260
|
+
for (const [key, val] of Object.entries(v)) {
|
|
261
|
+
result[key] = assertJsonValue(val);
|
|
262
|
+
}
|
|
263
|
+
return result;
|
|
264
|
+
}
|
|
265
|
+
throw new TypeError(`Conditional value is not a valid JsonValue: ${typeof v}`);
|
|
266
|
+
}
|
|
267
|
+
function buildFieldNode(name, type, required, annotations, constraints = []) {
|
|
268
|
+
return {
|
|
269
|
+
kind: "field",
|
|
270
|
+
name,
|
|
271
|
+
type,
|
|
272
|
+
required: required === true,
|
|
273
|
+
constraints,
|
|
274
|
+
annotations,
|
|
275
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function buildAnnotations(label, placeholder) {
|
|
279
|
+
const annotations = [];
|
|
280
|
+
if (label !== void 0) {
|
|
281
|
+
const a = {
|
|
282
|
+
kind: "annotation",
|
|
283
|
+
annotationKind: "displayName",
|
|
284
|
+
value: label,
|
|
285
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
286
|
+
};
|
|
287
|
+
annotations.push(a);
|
|
288
|
+
}
|
|
289
|
+
if (placeholder !== void 0) {
|
|
290
|
+
const a = {
|
|
291
|
+
kind: "annotation",
|
|
292
|
+
annotationKind: "placeholder",
|
|
293
|
+
value: placeholder,
|
|
294
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
295
|
+
};
|
|
296
|
+
annotations.push(a);
|
|
297
|
+
}
|
|
298
|
+
return annotations;
|
|
299
|
+
}
|
|
300
|
+
function buildObjectProperties(elements, insideConditional = false) {
|
|
301
|
+
const properties = [];
|
|
302
|
+
for (const el of elements) {
|
|
303
|
+
if (isField(el)) {
|
|
304
|
+
const fieldNode = canonicalizeField(el);
|
|
305
|
+
properties.push({
|
|
306
|
+
name: fieldNode.name,
|
|
307
|
+
type: fieldNode.type,
|
|
308
|
+
// Fields inside a conditional branch are always optional in the
|
|
309
|
+
// data schema, regardless of their `required` flag — the condition
|
|
310
|
+
// may not be met, so the field may be absent.
|
|
311
|
+
optional: insideConditional || !fieldNode.required,
|
|
312
|
+
constraints: fieldNode.constraints,
|
|
313
|
+
annotations: fieldNode.annotations,
|
|
314
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
315
|
+
});
|
|
316
|
+
} else if (isGroup(el)) {
|
|
317
|
+
properties.push(...buildObjectProperties(el.elements, insideConditional));
|
|
318
|
+
} else if (isConditional(el)) {
|
|
319
|
+
properties.push(...buildObjectProperties(el.elements, true));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return properties;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// src/canonicalize/tsdoc-canonicalizer.ts
|
|
326
|
+
var import_core2 = require("@formspec/core");
|
|
327
|
+
function canonicalizeTSDoc(analysis, source) {
|
|
328
|
+
const file = source?.file ?? "";
|
|
329
|
+
const provenance = {
|
|
330
|
+
surface: "tsdoc",
|
|
331
|
+
file,
|
|
332
|
+
line: 1,
|
|
333
|
+
column: 0
|
|
334
|
+
};
|
|
335
|
+
const elements = assembleElements(analysis.fields, analysis.fieldLayouts, provenance);
|
|
336
|
+
return {
|
|
337
|
+
kind: "form-ir",
|
|
338
|
+
irVersion: import_core2.IR_VERSION,
|
|
339
|
+
elements,
|
|
340
|
+
typeRegistry: analysis.typeRegistry,
|
|
341
|
+
provenance
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function assembleElements(fields, layouts, provenance) {
|
|
345
|
+
const elements = [];
|
|
346
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
347
|
+
const topLevelOrder = [];
|
|
348
|
+
for (let i = 0; i < fields.length; i++) {
|
|
349
|
+
const field = fields[i];
|
|
350
|
+
const layout = layouts[i];
|
|
351
|
+
if (!field || !layout) continue;
|
|
352
|
+
const element = wrapInConditional(field, layout, provenance);
|
|
353
|
+
if (layout.groupLabel !== void 0) {
|
|
354
|
+
const label = layout.groupLabel;
|
|
355
|
+
let groupElements = groupMap.get(label);
|
|
356
|
+
if (!groupElements) {
|
|
357
|
+
groupElements = [];
|
|
358
|
+
groupMap.set(label, groupElements);
|
|
359
|
+
topLevelOrder.push({ type: "group", label });
|
|
360
|
+
}
|
|
361
|
+
groupElements.push(element);
|
|
362
|
+
} else {
|
|
363
|
+
topLevelOrder.push({ type: "element", element });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
for (const entry of topLevelOrder) {
|
|
367
|
+
if (entry.type === "group") {
|
|
368
|
+
const groupElements = groupMap.get(entry.label);
|
|
369
|
+
if (groupElements) {
|
|
370
|
+
const groupNode = {
|
|
371
|
+
kind: "group",
|
|
372
|
+
label: entry.label,
|
|
373
|
+
elements: groupElements,
|
|
374
|
+
provenance
|
|
375
|
+
};
|
|
376
|
+
elements.push(groupNode);
|
|
377
|
+
groupMap.delete(entry.label);
|
|
378
|
+
}
|
|
379
|
+
} else {
|
|
380
|
+
elements.push(entry.element);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return elements;
|
|
384
|
+
}
|
|
385
|
+
function wrapInConditional(field, layout, provenance) {
|
|
386
|
+
if (layout.showWhen === void 0) {
|
|
387
|
+
return field;
|
|
388
|
+
}
|
|
389
|
+
const conditional = {
|
|
390
|
+
kind: "conditional",
|
|
391
|
+
fieldName: layout.showWhen.field,
|
|
392
|
+
value: layout.showWhen.value,
|
|
393
|
+
elements: [field],
|
|
394
|
+
provenance
|
|
395
|
+
};
|
|
396
|
+
return conditional;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// src/analyzer/program.ts
|
|
400
|
+
var ts = __toESM(require("typescript"), 1);
|
|
401
|
+
var path = __toESM(require("path"), 1);
|
|
402
|
+
function createProgramContext(filePath) {
|
|
403
|
+
const absolutePath = path.resolve(filePath);
|
|
404
|
+
const fileDir = path.dirname(absolutePath);
|
|
405
|
+
const configPath = ts.findConfigFile(fileDir, ts.sys.fileExists.bind(ts.sys), "tsconfig.json");
|
|
406
|
+
let compilerOptions;
|
|
407
|
+
let fileNames;
|
|
408
|
+
if (configPath) {
|
|
409
|
+
const configFile = ts.readConfigFile(configPath, ts.sys.readFile.bind(ts.sys));
|
|
410
|
+
if (configFile.error) {
|
|
411
|
+
throw new Error(
|
|
412
|
+
`Error reading tsconfig.json: ${ts.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
const parsed = ts.parseJsonConfigFileContent(
|
|
416
|
+
configFile.config,
|
|
417
|
+
ts.sys,
|
|
418
|
+
path.dirname(configPath)
|
|
419
|
+
);
|
|
420
|
+
if (parsed.errors.length > 0) {
|
|
421
|
+
const errorMessages = parsed.errors.map((e) => ts.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
|
|
422
|
+
throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
|
|
423
|
+
}
|
|
424
|
+
compilerOptions = parsed.options;
|
|
425
|
+
fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
|
|
426
|
+
} else {
|
|
427
|
+
compilerOptions = {
|
|
428
|
+
target: ts.ScriptTarget.ES2022,
|
|
429
|
+
module: ts.ModuleKind.NodeNext,
|
|
430
|
+
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
431
|
+
strict: true,
|
|
432
|
+
skipLibCheck: true,
|
|
433
|
+
declaration: true
|
|
434
|
+
};
|
|
435
|
+
fileNames = [absolutePath];
|
|
436
|
+
}
|
|
437
|
+
const program = ts.createProgram(fileNames, compilerOptions);
|
|
438
|
+
const sourceFile = program.getSourceFile(absolutePath);
|
|
439
|
+
if (!sourceFile) {
|
|
440
|
+
throw new Error(`Could not find source file: ${absolutePath}`);
|
|
441
|
+
}
|
|
442
|
+
return {
|
|
443
|
+
program,
|
|
444
|
+
checker: program.getTypeChecker(),
|
|
445
|
+
sourceFile
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
function findNodeByName(sourceFile, name, predicate, getName) {
|
|
449
|
+
let result = null;
|
|
450
|
+
function visit(node) {
|
|
451
|
+
if (result) return;
|
|
452
|
+
if (predicate(node) && getName(node) === name) {
|
|
453
|
+
result = node;
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
ts.forEachChild(node, visit);
|
|
457
|
+
}
|
|
458
|
+
visit(sourceFile);
|
|
459
|
+
return result;
|
|
460
|
+
}
|
|
461
|
+
function findClassByName(sourceFile, className) {
|
|
462
|
+
return findNodeByName(sourceFile, className, ts.isClassDeclaration, (n) => n.name?.text);
|
|
463
|
+
}
|
|
464
|
+
function findInterfaceByName(sourceFile, interfaceName) {
|
|
465
|
+
return findNodeByName(sourceFile, interfaceName, ts.isInterfaceDeclaration, (n) => n.name.text);
|
|
466
|
+
}
|
|
467
|
+
function findTypeAliasByName(sourceFile, aliasName) {
|
|
468
|
+
return findNodeByName(sourceFile, aliasName, ts.isTypeAliasDeclaration, (n) => n.name.text);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// src/analyzer/class-analyzer.ts
|
|
472
|
+
var ts4 = __toESM(require("typescript"), 1);
|
|
473
|
+
|
|
474
|
+
// src/analyzer/jsdoc-constraints.ts
|
|
475
|
+
var ts3 = __toESM(require("typescript"), 1);
|
|
476
|
+
var import_core4 = require("@formspec/core");
|
|
477
|
+
|
|
478
|
+
// src/analyzer/tsdoc-parser.ts
|
|
479
|
+
var ts2 = __toESM(require("typescript"), 1);
|
|
480
|
+
var import_tsdoc = require("@microsoft/tsdoc");
|
|
481
|
+
var import_core3 = require("@formspec/core");
|
|
482
|
+
var NUMERIC_CONSTRAINT_MAP = {
|
|
483
|
+
Minimum: "minimum",
|
|
484
|
+
Maximum: "maximum",
|
|
485
|
+
ExclusiveMinimum: "exclusiveMinimum",
|
|
486
|
+
ExclusiveMaximum: "exclusiveMaximum"
|
|
487
|
+
};
|
|
488
|
+
var LENGTH_CONSTRAINT_MAP = {
|
|
489
|
+
MinLength: "minLength",
|
|
490
|
+
MaxLength: "maxLength"
|
|
491
|
+
};
|
|
492
|
+
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["Pattern", "EnumOptions"]);
|
|
493
|
+
function isBuiltinConstraintName(tagName) {
|
|
494
|
+
return tagName in import_core3.BUILTIN_CONSTRAINT_DEFINITIONS;
|
|
495
|
+
}
|
|
496
|
+
function createFormSpecTSDocConfig() {
|
|
497
|
+
const config = new import_tsdoc.TSDocConfiguration();
|
|
498
|
+
for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
499
|
+
config.addTagDefinition(
|
|
500
|
+
new import_tsdoc.TSDocTagDefinition({
|
|
501
|
+
tagName: "@" + tagName,
|
|
502
|
+
syntaxKind: import_tsdoc.TSDocTagSyntaxKind.BlockTag,
|
|
503
|
+
allowMultiple: true
|
|
504
|
+
})
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
return config;
|
|
508
|
+
}
|
|
509
|
+
var sharedParser;
|
|
510
|
+
function getParser() {
|
|
511
|
+
sharedParser ??= new import_tsdoc.TSDocParser(createFormSpecTSDocConfig());
|
|
512
|
+
return sharedParser;
|
|
513
|
+
}
|
|
514
|
+
function parseTSDocTags(node, file = "") {
|
|
515
|
+
const constraints = [];
|
|
516
|
+
const annotations = [];
|
|
517
|
+
const sourceFile = node.getSourceFile();
|
|
518
|
+
const sourceText = sourceFile.getFullText();
|
|
519
|
+
const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
520
|
+
if (commentRanges) {
|
|
521
|
+
for (const range of commentRanges) {
|
|
522
|
+
if (range.kind !== ts2.SyntaxKind.MultiLineCommentTrivia) {
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
const commentText = sourceText.substring(range.pos, range.end);
|
|
526
|
+
if (!commentText.startsWith("/**")) {
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
const parser = getParser();
|
|
530
|
+
const parserContext = parser.parseRange(
|
|
531
|
+
import_tsdoc.TextRange.fromStringRange(sourceText, range.pos, range.end)
|
|
532
|
+
);
|
|
533
|
+
const docComment = parserContext.docComment;
|
|
534
|
+
for (const block of docComment.customBlocks) {
|
|
535
|
+
const tagName = block.blockTag.tagName.substring(1);
|
|
536
|
+
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
537
|
+
const text = extractBlockText(block).trim();
|
|
538
|
+
if (text === "") continue;
|
|
539
|
+
const provenance = provenanceForComment(range, sourceFile, file, tagName);
|
|
540
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
541
|
+
if (constraintNode) {
|
|
542
|
+
constraints.push(constraintNode);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (docComment.deprecatedBlock !== void 0) {
|
|
546
|
+
annotations.push({
|
|
547
|
+
kind: "annotation",
|
|
548
|
+
annotationKind: "deprecated",
|
|
549
|
+
provenance: provenanceForComment(range, sourceFile, file, "deprecated")
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
555
|
+
for (const tag of jsDocTagsAll) {
|
|
556
|
+
const tagName = tag.tagName.text;
|
|
557
|
+
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
558
|
+
const commentText = getTagCommentText(tag);
|
|
559
|
+
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
560
|
+
const text = commentText.trim();
|
|
561
|
+
const provenance = provenanceForJSDocTag(tag, file);
|
|
562
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
563
|
+
if (constraintNode) {
|
|
564
|
+
constraints.push(constraintNode);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
let displayName;
|
|
568
|
+
let description;
|
|
569
|
+
let displayNameTag;
|
|
570
|
+
let descriptionTag;
|
|
571
|
+
for (const tag of jsDocTagsAll) {
|
|
572
|
+
const tagName = tag.tagName.text;
|
|
573
|
+
const commentText = getTagCommentText(tag);
|
|
574
|
+
if (commentText === void 0 || commentText.trim() === "") {
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
const trimmed = commentText.trim();
|
|
578
|
+
if (tagName === "Field_displayName") {
|
|
579
|
+
displayName = trimmed;
|
|
580
|
+
displayNameTag = tag;
|
|
581
|
+
} else if (tagName === "Field_description") {
|
|
582
|
+
description = trimmed;
|
|
583
|
+
descriptionTag = tag;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
if (displayName !== void 0 && displayNameTag) {
|
|
587
|
+
annotations.push({
|
|
588
|
+
kind: "annotation",
|
|
589
|
+
annotationKind: "displayName",
|
|
590
|
+
value: displayName,
|
|
591
|
+
provenance: provenanceForJSDocTag(displayNameTag, file)
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
if (description !== void 0 && descriptionTag) {
|
|
595
|
+
annotations.push({
|
|
596
|
+
kind: "annotation",
|
|
597
|
+
annotationKind: "description",
|
|
598
|
+
value: description,
|
|
599
|
+
provenance: provenanceForJSDocTag(descriptionTag, file)
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
return { constraints, annotations };
|
|
603
|
+
}
|
|
604
|
+
function extractBlockText(block) {
|
|
605
|
+
return extractPlainText(block.content);
|
|
606
|
+
}
|
|
607
|
+
function extractPlainText(node) {
|
|
608
|
+
let result = "";
|
|
609
|
+
if (node instanceof import_tsdoc.DocPlainText) {
|
|
610
|
+
return node.text;
|
|
611
|
+
}
|
|
612
|
+
if (node instanceof import_tsdoc.DocSoftBreak) {
|
|
613
|
+
return " ";
|
|
614
|
+
}
|
|
615
|
+
if (typeof node.getChildNodes === "function") {
|
|
616
|
+
for (const child of node.getChildNodes()) {
|
|
617
|
+
result += extractPlainText(child);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return result;
|
|
621
|
+
}
|
|
622
|
+
function parseConstraintValue(tagName, text, provenance) {
|
|
623
|
+
if (!isBuiltinConstraintName(tagName)) {
|
|
624
|
+
return null;
|
|
625
|
+
}
|
|
626
|
+
const expectedType = import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
|
|
627
|
+
if (expectedType === "number") {
|
|
628
|
+
const value = Number(text);
|
|
629
|
+
if (Number.isNaN(value)) {
|
|
630
|
+
return null;
|
|
631
|
+
}
|
|
632
|
+
const numericKind = NUMERIC_CONSTRAINT_MAP[tagName];
|
|
633
|
+
if (numericKind) {
|
|
634
|
+
return {
|
|
635
|
+
kind: "constraint",
|
|
636
|
+
constraintKind: numericKind,
|
|
637
|
+
value,
|
|
638
|
+
provenance
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
const lengthKind = LENGTH_CONSTRAINT_MAP[tagName];
|
|
642
|
+
if (lengthKind) {
|
|
643
|
+
return {
|
|
644
|
+
kind: "constraint",
|
|
645
|
+
constraintKind: lengthKind,
|
|
646
|
+
value,
|
|
647
|
+
provenance
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
if (expectedType === "json") {
|
|
653
|
+
try {
|
|
654
|
+
const parsed = JSON.parse(text);
|
|
655
|
+
if (!Array.isArray(parsed)) {
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
const members = [];
|
|
659
|
+
for (const item of parsed) {
|
|
660
|
+
if (typeof item === "string" || typeof item === "number") {
|
|
661
|
+
members.push(item);
|
|
662
|
+
} else if (typeof item === "object" && item !== null && "id" in item) {
|
|
663
|
+
const id = item["id"];
|
|
664
|
+
if (typeof id === "string" || typeof id === "number") {
|
|
665
|
+
members.push(id);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return {
|
|
670
|
+
kind: "constraint",
|
|
671
|
+
constraintKind: "allowedMembers",
|
|
672
|
+
members,
|
|
673
|
+
provenance
|
|
674
|
+
};
|
|
675
|
+
} catch {
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
return {
|
|
680
|
+
kind: "constraint",
|
|
681
|
+
constraintKind: "pattern",
|
|
682
|
+
pattern: text,
|
|
683
|
+
provenance
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
function provenanceForComment(range, sourceFile, file, tagName) {
|
|
687
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
|
|
688
|
+
return {
|
|
689
|
+
surface: "tsdoc",
|
|
690
|
+
file,
|
|
691
|
+
line: line + 1,
|
|
692
|
+
column: character,
|
|
693
|
+
tagName: "@" + tagName
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
function provenanceForJSDocTag(tag, file) {
|
|
697
|
+
const sourceFile = tag.getSourceFile();
|
|
698
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(tag.getStart());
|
|
699
|
+
return {
|
|
700
|
+
surface: "tsdoc",
|
|
701
|
+
file,
|
|
702
|
+
line: line + 1,
|
|
703
|
+
column: character,
|
|
704
|
+
tagName: "@" + tag.tagName.text
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
function getTagCommentText(tag) {
|
|
708
|
+
if (tag.comment === void 0) {
|
|
709
|
+
return void 0;
|
|
710
|
+
}
|
|
711
|
+
if (typeof tag.comment === "string") {
|
|
712
|
+
return tag.comment;
|
|
713
|
+
}
|
|
714
|
+
return ts2.getTextOfJSDocComment(tag.comment);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// src/analyzer/jsdoc-constraints.ts
|
|
718
|
+
function extractJSDocConstraintNodes(node, file = "") {
|
|
719
|
+
const result = parseTSDocTags(node, file);
|
|
720
|
+
return [...result.constraints];
|
|
721
|
+
}
|
|
722
|
+
function extractJSDocAnnotationNodes(node, file = "") {
|
|
723
|
+
const result = parseTSDocTags(node, file);
|
|
724
|
+
return [...result.annotations];
|
|
725
|
+
}
|
|
726
|
+
function extractDefaultValueAnnotation(initializer, file = "") {
|
|
727
|
+
if (!initializer) return null;
|
|
728
|
+
let value;
|
|
729
|
+
if (ts3.isStringLiteral(initializer)) {
|
|
730
|
+
value = initializer.text;
|
|
731
|
+
} else if (ts3.isNumericLiteral(initializer)) {
|
|
732
|
+
value = Number(initializer.text);
|
|
733
|
+
} else if (initializer.kind === ts3.SyntaxKind.TrueKeyword) {
|
|
734
|
+
value = true;
|
|
735
|
+
} else if (initializer.kind === ts3.SyntaxKind.FalseKeyword) {
|
|
736
|
+
value = false;
|
|
737
|
+
} else if (initializer.kind === ts3.SyntaxKind.NullKeyword) {
|
|
738
|
+
value = null;
|
|
739
|
+
} else if (ts3.isPrefixUnaryExpression(initializer)) {
|
|
740
|
+
if (initializer.operator === ts3.SyntaxKind.MinusToken && ts3.isNumericLiteral(initializer.operand)) {
|
|
741
|
+
value = -Number(initializer.operand.text);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
if (value === void 0) return null;
|
|
745
|
+
const sourceFile = initializer.getSourceFile();
|
|
746
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(initializer.getStart());
|
|
747
|
+
return {
|
|
748
|
+
kind: "annotation",
|
|
749
|
+
annotationKind: "defaultValue",
|
|
750
|
+
value,
|
|
751
|
+
provenance: {
|
|
752
|
+
surface: "tsdoc",
|
|
753
|
+
file,
|
|
754
|
+
line: line + 1,
|
|
755
|
+
column: character
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// src/analyzer/class-analyzer.ts
|
|
761
|
+
function isObjectType(type) {
|
|
762
|
+
return !!(type.flags & ts4.TypeFlags.Object);
|
|
763
|
+
}
|
|
764
|
+
function isTypeReference(type) {
|
|
765
|
+
return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
|
|
766
|
+
}
|
|
767
|
+
function analyzeClassToIR(classDecl, checker, file = "") {
|
|
768
|
+
const name = classDecl.name?.text ?? "AnonymousClass";
|
|
769
|
+
const fields = [];
|
|
770
|
+
const fieldLayouts = [];
|
|
771
|
+
const typeRegistry = {};
|
|
772
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
773
|
+
const instanceMethods = [];
|
|
774
|
+
const staticMethods = [];
|
|
775
|
+
for (const member of classDecl.members) {
|
|
776
|
+
if (ts4.isPropertyDeclaration(member)) {
|
|
777
|
+
const fieldNode = analyzeFieldToIR(member, checker, file, typeRegistry, visiting);
|
|
778
|
+
if (fieldNode) {
|
|
779
|
+
fields.push(fieldNode);
|
|
780
|
+
fieldLayouts.push({});
|
|
781
|
+
}
|
|
782
|
+
} else if (ts4.isMethodDeclaration(member)) {
|
|
783
|
+
const methodInfo = analyzeMethod(member, checker);
|
|
784
|
+
if (methodInfo) {
|
|
785
|
+
const isStatic = member.modifiers?.some((m) => m.kind === ts4.SyntaxKind.StaticKeyword);
|
|
786
|
+
if (isStatic) {
|
|
787
|
+
staticMethods.push(methodInfo);
|
|
788
|
+
} else {
|
|
789
|
+
instanceMethods.push(methodInfo);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return { name, fields, fieldLayouts, typeRegistry, instanceMethods, staticMethods };
|
|
795
|
+
}
|
|
796
|
+
function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
797
|
+
const name = interfaceDecl.name.text;
|
|
798
|
+
const fields = [];
|
|
799
|
+
const typeRegistry = {};
|
|
800
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
801
|
+
for (const member of interfaceDecl.members) {
|
|
802
|
+
if (ts4.isPropertySignature(member)) {
|
|
803
|
+
const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
|
|
804
|
+
if (fieldNode) {
|
|
805
|
+
fields.push(fieldNode);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
const fieldLayouts = fields.map(() => ({}));
|
|
810
|
+
return { name, fields, fieldLayouts, typeRegistry, instanceMethods: [], staticMethods: [] };
|
|
811
|
+
}
|
|
812
|
+
function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
813
|
+
if (!ts4.isTypeLiteralNode(typeAlias.type)) {
|
|
814
|
+
const sourceFile = typeAlias.getSourceFile();
|
|
815
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
|
|
816
|
+
const kindDesc = ts4.SyntaxKind[typeAlias.type.kind] ?? "unknown";
|
|
817
|
+
return {
|
|
818
|
+
ok: false,
|
|
819
|
+
error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
const name = typeAlias.name.text;
|
|
823
|
+
const fields = [];
|
|
824
|
+
const typeRegistry = {};
|
|
825
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
826
|
+
for (const member of typeAlias.type.members) {
|
|
827
|
+
if (ts4.isPropertySignature(member)) {
|
|
828
|
+
const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
|
|
829
|
+
if (fieldNode) {
|
|
830
|
+
fields.push(fieldNode);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
return {
|
|
835
|
+
ok: true,
|
|
836
|
+
analysis: {
|
|
837
|
+
name,
|
|
838
|
+
fields,
|
|
839
|
+
fieldLayouts: fields.map(() => ({})),
|
|
840
|
+
typeRegistry,
|
|
841
|
+
instanceMethods: [],
|
|
842
|
+
staticMethods: []
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
847
|
+
if (!ts4.isIdentifier(prop.name)) {
|
|
848
|
+
return null;
|
|
849
|
+
}
|
|
850
|
+
const name = prop.name.text;
|
|
851
|
+
const tsType = checker.getTypeAtLocation(prop);
|
|
852
|
+
const optional = prop.questionToken !== void 0;
|
|
853
|
+
const provenance = provenanceForNode(prop, file);
|
|
854
|
+
const type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
|
|
855
|
+
const constraints = [];
|
|
856
|
+
if (prop.type) {
|
|
857
|
+
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
858
|
+
}
|
|
859
|
+
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
860
|
+
const annotations = [];
|
|
861
|
+
annotations.push(...extractJSDocAnnotationNodes(prop, file));
|
|
862
|
+
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
863
|
+
if (defaultAnnotation) {
|
|
864
|
+
annotations.push(defaultAnnotation);
|
|
865
|
+
}
|
|
866
|
+
return {
|
|
867
|
+
kind: "field",
|
|
868
|
+
name,
|
|
869
|
+
type,
|
|
870
|
+
required: !optional,
|
|
871
|
+
constraints,
|
|
872
|
+
annotations,
|
|
873
|
+
provenance
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting) {
|
|
877
|
+
if (!ts4.isIdentifier(prop.name)) {
|
|
878
|
+
return null;
|
|
879
|
+
}
|
|
880
|
+
const name = prop.name.text;
|
|
881
|
+
const tsType = checker.getTypeAtLocation(prop);
|
|
882
|
+
const optional = prop.questionToken !== void 0;
|
|
883
|
+
const provenance = provenanceForNode(prop, file);
|
|
884
|
+
const type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
|
|
885
|
+
const constraints = [];
|
|
886
|
+
if (prop.type) {
|
|
887
|
+
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
888
|
+
}
|
|
889
|
+
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
890
|
+
const annotations = [];
|
|
891
|
+
annotations.push(...extractJSDocAnnotationNodes(prop, file));
|
|
892
|
+
return {
|
|
893
|
+
kind: "field",
|
|
894
|
+
name,
|
|
895
|
+
type,
|
|
896
|
+
required: !optional,
|
|
897
|
+
constraints,
|
|
898
|
+
annotations,
|
|
899
|
+
provenance
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
903
|
+
if (type.flags & ts4.TypeFlags.String) {
|
|
904
|
+
return { kind: "primitive", primitiveKind: "string" };
|
|
905
|
+
}
|
|
906
|
+
if (type.flags & ts4.TypeFlags.Number) {
|
|
907
|
+
return { kind: "primitive", primitiveKind: "number" };
|
|
908
|
+
}
|
|
909
|
+
if (type.flags & ts4.TypeFlags.Boolean) {
|
|
910
|
+
return { kind: "primitive", primitiveKind: "boolean" };
|
|
911
|
+
}
|
|
912
|
+
if (type.flags & ts4.TypeFlags.Null) {
|
|
913
|
+
return { kind: "primitive", primitiveKind: "null" };
|
|
914
|
+
}
|
|
915
|
+
if (type.flags & ts4.TypeFlags.Undefined) {
|
|
916
|
+
return { kind: "primitive", primitiveKind: "null" };
|
|
917
|
+
}
|
|
918
|
+
if (type.isStringLiteral()) {
|
|
919
|
+
return {
|
|
920
|
+
kind: "enum",
|
|
921
|
+
members: [{ value: type.value }]
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
if (type.isNumberLiteral()) {
|
|
925
|
+
return {
|
|
926
|
+
kind: "enum",
|
|
927
|
+
members: [{ value: type.value }]
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
if (type.isUnion()) {
|
|
931
|
+
return resolveUnionType(type, checker, file, typeRegistry, visiting);
|
|
932
|
+
}
|
|
933
|
+
if (checker.isArrayType(type)) {
|
|
934
|
+
return resolveArrayType(type, checker, file, typeRegistry, visiting);
|
|
935
|
+
}
|
|
936
|
+
if (isObjectType(type)) {
|
|
937
|
+
return resolveObjectType(type, checker, file, typeRegistry, visiting);
|
|
938
|
+
}
|
|
939
|
+
return { kind: "primitive", primitiveKind: "string" };
|
|
940
|
+
}
|
|
941
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting) {
|
|
942
|
+
const allTypes = type.types;
|
|
943
|
+
const nonNullTypes = allTypes.filter(
|
|
944
|
+
(t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
|
|
945
|
+
);
|
|
946
|
+
const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
|
|
947
|
+
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
|
|
948
|
+
if (isBooleanUnion2) {
|
|
949
|
+
const boolNode = { kind: "primitive", primitiveKind: "boolean" };
|
|
950
|
+
if (hasNull) {
|
|
951
|
+
return {
|
|
952
|
+
kind: "union",
|
|
953
|
+
members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
return boolNode;
|
|
957
|
+
}
|
|
958
|
+
const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
|
|
959
|
+
if (allStringLiterals && nonNullTypes.length > 0) {
|
|
960
|
+
const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
|
|
961
|
+
const enumNode = {
|
|
962
|
+
kind: "enum",
|
|
963
|
+
members: stringTypes.map((t) => ({ value: t.value }))
|
|
964
|
+
};
|
|
965
|
+
if (hasNull) {
|
|
966
|
+
return {
|
|
967
|
+
kind: "union",
|
|
968
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
return enumNode;
|
|
972
|
+
}
|
|
973
|
+
const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
|
|
974
|
+
if (allNumberLiterals && nonNullTypes.length > 0) {
|
|
975
|
+
const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
|
|
976
|
+
const enumNode = {
|
|
977
|
+
kind: "enum",
|
|
978
|
+
members: numberTypes.map((t) => ({ value: t.value }))
|
|
979
|
+
};
|
|
980
|
+
if (hasNull) {
|
|
981
|
+
return {
|
|
982
|
+
kind: "union",
|
|
983
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
return enumNode;
|
|
987
|
+
}
|
|
988
|
+
if (nonNullTypes.length === 1 && nonNullTypes[0]) {
|
|
989
|
+
const inner = resolveTypeNode(nonNullTypes[0], checker, file, typeRegistry, visiting);
|
|
990
|
+
if (hasNull) {
|
|
991
|
+
return {
|
|
992
|
+
kind: "union",
|
|
993
|
+
members: [inner, { kind: "primitive", primitiveKind: "null" }]
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
return inner;
|
|
997
|
+
}
|
|
998
|
+
const members = nonNullTypes.map(
|
|
999
|
+
(t) => resolveTypeNode(t, checker, file, typeRegistry, visiting)
|
|
1000
|
+
);
|
|
1001
|
+
if (hasNull) {
|
|
1002
|
+
members.push({ kind: "primitive", primitiveKind: "null" });
|
|
1003
|
+
}
|
|
1004
|
+
return { kind: "union", members };
|
|
1005
|
+
}
|
|
1006
|
+
function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
1007
|
+
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
1008
|
+
const elementType = typeArgs?.[0];
|
|
1009
|
+
const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
|
|
1010
|
+
return { kind: "array", items };
|
|
1011
|
+
}
|
|
1012
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
1013
|
+
if (visiting.has(type)) {
|
|
1014
|
+
return { kind: "object", properties: [], additionalProperties: false };
|
|
1015
|
+
}
|
|
1016
|
+
visiting.add(type);
|
|
1017
|
+
const typeName = getNamedTypeName(type);
|
|
1018
|
+
if (typeName && typeName in typeRegistry) {
|
|
1019
|
+
visiting.delete(type);
|
|
1020
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
1021
|
+
}
|
|
1022
|
+
const properties = [];
|
|
1023
|
+
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
|
|
1024
|
+
for (const prop of type.getProperties()) {
|
|
1025
|
+
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
1026
|
+
if (!declaration) continue;
|
|
1027
|
+
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
1028
|
+
const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
|
|
1029
|
+
const propTypeNode = resolveTypeNode(propType, checker, file, typeRegistry, visiting);
|
|
1030
|
+
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
1031
|
+
properties.push({
|
|
1032
|
+
name: prop.name,
|
|
1033
|
+
type: propTypeNode,
|
|
1034
|
+
optional,
|
|
1035
|
+
constraints: fieldNodeInfo?.constraints ?? [],
|
|
1036
|
+
annotations: fieldNodeInfo?.annotations ?? [],
|
|
1037
|
+
provenance: fieldNodeInfo?.provenance ?? provenanceForFile(file)
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
visiting.delete(type);
|
|
1041
|
+
const objectNode = {
|
|
1042
|
+
kind: "object",
|
|
1043
|
+
properties,
|
|
1044
|
+
additionalProperties: false
|
|
1045
|
+
};
|
|
1046
|
+
if (typeName) {
|
|
1047
|
+
typeRegistry[typeName] = {
|
|
1048
|
+
name: typeName,
|
|
1049
|
+
type: objectNode,
|
|
1050
|
+
provenance: provenanceForFile(file)
|
|
1051
|
+
};
|
|
1052
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
1053
|
+
}
|
|
1054
|
+
return objectNode;
|
|
1055
|
+
}
|
|
1056
|
+
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting) {
|
|
1057
|
+
const symbols = [type.getSymbol(), type.aliasSymbol].filter(
|
|
1058
|
+
(s) => s?.declarations != null && s.declarations.length > 0
|
|
1059
|
+
);
|
|
1060
|
+
for (const symbol of symbols) {
|
|
1061
|
+
const declarations = symbol.declarations;
|
|
1062
|
+
if (!declarations) continue;
|
|
1063
|
+
const classDecl = declarations.find(ts4.isClassDeclaration);
|
|
1064
|
+
if (classDecl) {
|
|
1065
|
+
const map = /* @__PURE__ */ new Map();
|
|
1066
|
+
for (const member of classDecl.members) {
|
|
1067
|
+
if (ts4.isPropertyDeclaration(member) && ts4.isIdentifier(member.name)) {
|
|
1068
|
+
const fieldNode = analyzeFieldToIR(member, checker, file, typeRegistry, visiting);
|
|
1069
|
+
if (fieldNode) {
|
|
1070
|
+
map.set(fieldNode.name, {
|
|
1071
|
+
constraints: [...fieldNode.constraints],
|
|
1072
|
+
annotations: [...fieldNode.annotations],
|
|
1073
|
+
provenance: fieldNode.provenance
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
return map;
|
|
1079
|
+
}
|
|
1080
|
+
const interfaceDecl = declarations.find(ts4.isInterfaceDeclaration);
|
|
1081
|
+
if (interfaceDecl) {
|
|
1082
|
+
return buildFieldNodeInfoMap(interfaceDecl.members, checker, file, typeRegistry, visiting);
|
|
1083
|
+
}
|
|
1084
|
+
const typeAliasDecl = declarations.find(ts4.isTypeAliasDeclaration);
|
|
1085
|
+
if (typeAliasDecl && ts4.isTypeLiteralNode(typeAliasDecl.type)) {
|
|
1086
|
+
return buildFieldNodeInfoMap(
|
|
1087
|
+
typeAliasDecl.type.members,
|
|
1088
|
+
checker,
|
|
1089
|
+
file,
|
|
1090
|
+
typeRegistry,
|
|
1091
|
+
visiting
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
return null;
|
|
1096
|
+
}
|
|
1097
|
+
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
1098
|
+
const map = /* @__PURE__ */ new Map();
|
|
1099
|
+
for (const member of members) {
|
|
1100
|
+
if (ts4.isPropertySignature(member)) {
|
|
1101
|
+
const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
|
|
1102
|
+
if (fieldNode) {
|
|
1103
|
+
map.set(fieldNode.name, {
|
|
1104
|
+
constraints: [...fieldNode.constraints],
|
|
1105
|
+
annotations: [...fieldNode.annotations],
|
|
1106
|
+
provenance: fieldNode.provenance
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
return map;
|
|
1112
|
+
}
|
|
1113
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file) {
|
|
1114
|
+
if (!ts4.isTypeReferenceNode(typeNode)) return [];
|
|
1115
|
+
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
1116
|
+
if (!symbol?.declarations) return [];
|
|
1117
|
+
const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1118
|
+
if (!aliasDecl) return [];
|
|
1119
|
+
if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
1120
|
+
return extractJSDocConstraintNodes(aliasDecl, file);
|
|
1121
|
+
}
|
|
1122
|
+
function provenanceForNode(node, file) {
|
|
1123
|
+
const sourceFile = node.getSourceFile();
|
|
1124
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
1125
|
+
return {
|
|
1126
|
+
surface: "tsdoc",
|
|
1127
|
+
file,
|
|
1128
|
+
line: line + 1,
|
|
1129
|
+
column: character
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
function provenanceForFile(file) {
|
|
1133
|
+
return { surface: "tsdoc", file, line: 0, column: 0 };
|
|
1134
|
+
}
|
|
1135
|
+
function getNamedTypeName(type) {
|
|
1136
|
+
const symbol = type.getSymbol();
|
|
1137
|
+
if (symbol?.declarations) {
|
|
1138
|
+
const decl = symbol.declarations[0];
|
|
1139
|
+
if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
|
|
1140
|
+
const name = ts4.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
|
|
1141
|
+
if (name) return name;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
const aliasSymbol = type.aliasSymbol;
|
|
1145
|
+
if (aliasSymbol?.declarations) {
|
|
1146
|
+
const aliasDecl = aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1147
|
+
if (aliasDecl) {
|
|
1148
|
+
return aliasDecl.name.text;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
return null;
|
|
1152
|
+
}
|
|
1153
|
+
function analyzeMethod(method, checker) {
|
|
1154
|
+
if (!ts4.isIdentifier(method.name)) {
|
|
1155
|
+
return null;
|
|
1156
|
+
}
|
|
1157
|
+
const name = method.name.text;
|
|
1158
|
+
const parameters = [];
|
|
1159
|
+
for (const param of method.parameters) {
|
|
1160
|
+
if (ts4.isIdentifier(param.name)) {
|
|
1161
|
+
const paramInfo = analyzeParameter(param, checker);
|
|
1162
|
+
parameters.push(paramInfo);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
const returnTypeNode = method.type;
|
|
1166
|
+
const signature = checker.getSignatureFromDeclaration(method);
|
|
1167
|
+
const returnType = signature ? checker.getReturnTypeOfSignature(signature) : checker.getTypeAtLocation(method);
|
|
1168
|
+
return { name, parameters, returnTypeNode, returnType };
|
|
1169
|
+
}
|
|
1170
|
+
function analyzeParameter(param, checker) {
|
|
1171
|
+
const name = ts4.isIdentifier(param.name) ? param.name.text : "param";
|
|
1172
|
+
const typeNode = param.type;
|
|
1173
|
+
const type = checker.getTypeAtLocation(param);
|
|
1174
|
+
const formSpecExportName = detectFormSpecReference(typeNode);
|
|
1175
|
+
const optional = param.questionToken !== void 0 || param.initializer !== void 0;
|
|
1176
|
+
return { name, typeNode, type, formSpecExportName, optional };
|
|
1177
|
+
}
|
|
1178
|
+
function detectFormSpecReference(typeNode) {
|
|
1179
|
+
if (!typeNode) return null;
|
|
1180
|
+
if (!ts4.isTypeReferenceNode(typeNode)) return null;
|
|
1181
|
+
const typeName = ts4.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts4.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
|
|
1182
|
+
if (typeName !== "InferSchema" && typeName !== "InferFormSchema") return null;
|
|
1183
|
+
const typeArg = typeNode.typeArguments?.[0];
|
|
1184
|
+
if (!typeArg || !ts4.isTypeQueryNode(typeArg)) return null;
|
|
1185
|
+
if (ts4.isIdentifier(typeArg.exprName)) {
|
|
1186
|
+
return typeArg.exprName.text;
|
|
1187
|
+
}
|
|
1188
|
+
if (ts4.isQualifiedName(typeArg.exprName)) {
|
|
1189
|
+
return typeArg.exprName.right.text;
|
|
1190
|
+
}
|
|
1191
|
+
return null;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// src/json-schema/ir-generator.ts
|
|
1195
|
+
function makeContext() {
|
|
1196
|
+
return { defs: {} };
|
|
1197
|
+
}
|
|
1198
|
+
function generateJsonSchemaFromIR(ir) {
|
|
1199
|
+
const ctx = makeContext();
|
|
1200
|
+
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
1201
|
+
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
1202
|
+
}
|
|
1203
|
+
const properties = {};
|
|
1204
|
+
const required = [];
|
|
1205
|
+
collectFields(ir.elements, properties, required, ctx);
|
|
1206
|
+
const uniqueRequired = [...new Set(required)];
|
|
1207
|
+
const result = {
|
|
1208
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
1209
|
+
type: "object",
|
|
1210
|
+
properties,
|
|
1211
|
+
...uniqueRequired.length > 0 && { required: uniqueRequired }
|
|
1212
|
+
};
|
|
1213
|
+
if (Object.keys(ctx.defs).length > 0) {
|
|
1214
|
+
result.$defs = ctx.defs;
|
|
1215
|
+
}
|
|
1216
|
+
return result;
|
|
1217
|
+
}
|
|
1218
|
+
function collectFields(elements, properties, required, ctx) {
|
|
1219
|
+
for (const element of elements) {
|
|
1220
|
+
switch (element.kind) {
|
|
1221
|
+
case "field":
|
|
1222
|
+
properties[element.name] = generateFieldSchema(element, ctx);
|
|
1223
|
+
if (element.required) {
|
|
1224
|
+
required.push(element.name);
|
|
1225
|
+
}
|
|
1226
|
+
break;
|
|
1227
|
+
case "group":
|
|
1228
|
+
collectFields(element.elements, properties, required, ctx);
|
|
1229
|
+
break;
|
|
1230
|
+
case "conditional":
|
|
1231
|
+
collectFields(element.elements, properties, required, ctx);
|
|
1232
|
+
break;
|
|
1233
|
+
default: {
|
|
1234
|
+
const _exhaustive = element;
|
|
1235
|
+
void _exhaustive;
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
function generateFieldSchema(field, ctx) {
|
|
1241
|
+
const schema = generateTypeNode(field.type, ctx);
|
|
1242
|
+
applyConstraints(schema, field.constraints);
|
|
1243
|
+
applyAnnotations(schema, field.annotations);
|
|
1244
|
+
return schema;
|
|
1245
|
+
}
|
|
1246
|
+
function generateTypeNode(type, ctx) {
|
|
1247
|
+
switch (type.kind) {
|
|
1248
|
+
case "primitive":
|
|
1249
|
+
return generatePrimitiveType(type);
|
|
1250
|
+
case "enum":
|
|
1251
|
+
return generateEnumType(type);
|
|
1252
|
+
case "array":
|
|
1253
|
+
return generateArrayType(type, ctx);
|
|
1254
|
+
case "object":
|
|
1255
|
+
return generateObjectType(type, ctx);
|
|
1256
|
+
case "union":
|
|
1257
|
+
return generateUnionType(type, ctx);
|
|
1258
|
+
case "reference":
|
|
1259
|
+
return generateReferenceType(type);
|
|
1260
|
+
case "dynamic":
|
|
1261
|
+
return generateDynamicType(type);
|
|
1262
|
+
case "custom":
|
|
1263
|
+
return generateCustomType(type);
|
|
1264
|
+
default: {
|
|
1265
|
+
const _exhaustive = type;
|
|
1266
|
+
return _exhaustive;
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
function generatePrimitiveType(type) {
|
|
1271
|
+
return { type: type.primitiveKind };
|
|
1272
|
+
}
|
|
1273
|
+
function generateEnumType(type) {
|
|
1274
|
+
const hasDisplayNames = type.members.some((m) => m.displayName !== void 0);
|
|
1275
|
+
if (hasDisplayNames) {
|
|
1276
|
+
return {
|
|
1277
|
+
oneOf: type.members.map((m) => {
|
|
1278
|
+
const entry = { const: m.value };
|
|
1279
|
+
if (m.displayName !== void 0) {
|
|
1280
|
+
entry.title = m.displayName;
|
|
1281
|
+
}
|
|
1282
|
+
return entry;
|
|
1283
|
+
})
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
return { enum: type.members.map((m) => m.value) };
|
|
1287
|
+
}
|
|
1288
|
+
function generateArrayType(type, ctx) {
|
|
1289
|
+
return {
|
|
1290
|
+
type: "array",
|
|
1291
|
+
items: generateTypeNode(type.items, ctx)
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
function generateObjectType(type, ctx) {
|
|
1295
|
+
const properties = {};
|
|
1296
|
+
const required = [];
|
|
1297
|
+
for (const prop of type.properties) {
|
|
1298
|
+
properties[prop.name] = generatePropertySchema(prop, ctx);
|
|
1299
|
+
if (!prop.optional) {
|
|
1300
|
+
required.push(prop.name);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
const schema = { type: "object", properties };
|
|
1304
|
+
if (required.length > 0) {
|
|
1305
|
+
schema.required = required;
|
|
1306
|
+
}
|
|
1307
|
+
if (!type.additionalProperties) {
|
|
1308
|
+
schema.additionalProperties = false;
|
|
1309
|
+
}
|
|
1310
|
+
return schema;
|
|
1311
|
+
}
|
|
1312
|
+
function generatePropertySchema(prop, ctx) {
|
|
1313
|
+
const schema = generateTypeNode(prop.type, ctx);
|
|
1314
|
+
applyConstraints(schema, prop.constraints);
|
|
1315
|
+
applyAnnotations(schema, prop.annotations);
|
|
1316
|
+
return schema;
|
|
1317
|
+
}
|
|
1318
|
+
function generateUnionType(type, ctx) {
|
|
1319
|
+
if (isBooleanUnion(type)) {
|
|
1320
|
+
return { type: "boolean" };
|
|
1321
|
+
}
|
|
1322
|
+
return {
|
|
1323
|
+
anyOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
function isBooleanUnion(type) {
|
|
1327
|
+
if (type.members.length !== 2) return false;
|
|
1328
|
+
const kinds = type.members.map((m) => m.kind);
|
|
1329
|
+
return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
|
|
1330
|
+
}
|
|
1331
|
+
function generateReferenceType(type) {
|
|
1332
|
+
return { $ref: `#/$defs/${type.name}` };
|
|
1333
|
+
}
|
|
1334
|
+
function generateDynamicType(type) {
|
|
1335
|
+
if (type.dynamicKind === "enum") {
|
|
1336
|
+
const schema = {
|
|
1337
|
+
type: "string",
|
|
1338
|
+
"x-formspec-source": type.sourceKey
|
|
1339
|
+
};
|
|
1340
|
+
if (type.parameterFields.length > 0) {
|
|
1341
|
+
schema["x-formspec-params"] = [...type.parameterFields];
|
|
1342
|
+
}
|
|
1343
|
+
return schema;
|
|
1344
|
+
}
|
|
1345
|
+
return {
|
|
1346
|
+
type: "object",
|
|
1347
|
+
additionalProperties: true,
|
|
1348
|
+
"x-formspec-schemaSource": type.sourceKey
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
function generateCustomType(_type) {
|
|
1352
|
+
return { type: "object" };
|
|
1353
|
+
}
|
|
1354
|
+
function applyConstraints(schema, constraints) {
|
|
1355
|
+
for (const constraint of constraints) {
|
|
1356
|
+
switch (constraint.constraintKind) {
|
|
1357
|
+
case "minimum":
|
|
1358
|
+
schema.minimum = constraint.value;
|
|
1359
|
+
break;
|
|
1360
|
+
case "maximum":
|
|
1361
|
+
schema.maximum = constraint.value;
|
|
1362
|
+
break;
|
|
1363
|
+
case "exclusiveMinimum":
|
|
1364
|
+
schema.exclusiveMinimum = constraint.value;
|
|
1365
|
+
break;
|
|
1366
|
+
case "exclusiveMaximum":
|
|
1367
|
+
schema.exclusiveMaximum = constraint.value;
|
|
1368
|
+
break;
|
|
1369
|
+
case "multipleOf": {
|
|
1370
|
+
const { value } = constraint;
|
|
1371
|
+
if (value === 1 && schema.type === "number") {
|
|
1372
|
+
schema.type = "integer";
|
|
1373
|
+
} else {
|
|
1374
|
+
schema.multipleOf = value;
|
|
1375
|
+
}
|
|
1376
|
+
break;
|
|
1377
|
+
}
|
|
1378
|
+
case "minLength":
|
|
1379
|
+
schema.minLength = constraint.value;
|
|
1380
|
+
break;
|
|
1381
|
+
case "maxLength":
|
|
1382
|
+
schema.maxLength = constraint.value;
|
|
1383
|
+
break;
|
|
1384
|
+
case "minItems":
|
|
1385
|
+
schema.minItems = constraint.value;
|
|
1386
|
+
break;
|
|
1387
|
+
case "maxItems":
|
|
1388
|
+
schema.maxItems = constraint.value;
|
|
1389
|
+
break;
|
|
1390
|
+
case "pattern":
|
|
1391
|
+
schema.pattern = constraint.pattern;
|
|
1392
|
+
break;
|
|
1393
|
+
case "uniqueItems":
|
|
1394
|
+
schema.uniqueItems = constraint.value;
|
|
1395
|
+
break;
|
|
1396
|
+
case "allowedMembers":
|
|
1397
|
+
break;
|
|
1398
|
+
case "custom":
|
|
1399
|
+
break;
|
|
1400
|
+
default: {
|
|
1401
|
+
const _exhaustive = constraint;
|
|
1402
|
+
void _exhaustive;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
function applyAnnotations(schema, annotations) {
|
|
1408
|
+
for (const annotation of annotations) {
|
|
1409
|
+
switch (annotation.annotationKind) {
|
|
1410
|
+
case "displayName":
|
|
1411
|
+
schema.title = annotation.value;
|
|
1412
|
+
break;
|
|
1413
|
+
case "description":
|
|
1414
|
+
schema.description = annotation.value;
|
|
1415
|
+
break;
|
|
1416
|
+
case "defaultValue":
|
|
1417
|
+
schema.default = annotation.value;
|
|
1418
|
+
break;
|
|
1419
|
+
case "deprecated":
|
|
1420
|
+
schema.deprecated = true;
|
|
1421
|
+
break;
|
|
1422
|
+
case "placeholder":
|
|
1423
|
+
break;
|
|
1424
|
+
case "formatHint":
|
|
1425
|
+
break;
|
|
1426
|
+
case "custom":
|
|
1427
|
+
break;
|
|
1428
|
+
default: {
|
|
1429
|
+
const _exhaustive = annotation;
|
|
1430
|
+
void _exhaustive;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
// src/ui-schema/schema.ts
|
|
1437
|
+
var import_zod = require("zod");
|
|
1438
|
+
var jsonPointerSchema = import_zod.z.string();
|
|
1439
|
+
var ruleEffectSchema = import_zod.z.enum(["SHOW", "HIDE", "ENABLE", "DISABLE"]);
|
|
1440
|
+
var uiSchemaElementTypeSchema = import_zod.z.enum([
|
|
1441
|
+
"Control",
|
|
1442
|
+
"VerticalLayout",
|
|
1443
|
+
"HorizontalLayout",
|
|
1444
|
+
"Group",
|
|
1445
|
+
"Categorization",
|
|
1446
|
+
"Category",
|
|
1447
|
+
"Label"
|
|
1448
|
+
]);
|
|
1449
|
+
var ruleConditionSchema = import_zod.z.lazy(
|
|
1450
|
+
() => import_zod.z.object({
|
|
1451
|
+
const: import_zod.z.unknown().optional(),
|
|
1452
|
+
enum: import_zod.z.array(import_zod.z.unknown()).readonly().optional(),
|
|
1453
|
+
type: import_zod.z.string().optional(),
|
|
1454
|
+
not: ruleConditionSchema.optional(),
|
|
1455
|
+
minimum: import_zod.z.number().optional(),
|
|
1456
|
+
maximum: import_zod.z.number().optional(),
|
|
1457
|
+
exclusiveMinimum: import_zod.z.number().optional(),
|
|
1458
|
+
exclusiveMaximum: import_zod.z.number().optional(),
|
|
1459
|
+
minLength: import_zod.z.number().optional(),
|
|
1460
|
+
properties: import_zod.z.record(import_zod.z.string(), ruleConditionSchema).optional(),
|
|
1461
|
+
required: import_zod.z.array(import_zod.z.string()).optional(),
|
|
1462
|
+
allOf: import_zod.z.array(ruleConditionSchema).optional()
|
|
1463
|
+
}).strict()
|
|
1464
|
+
);
|
|
1465
|
+
var schemaBasedConditionSchema = import_zod.z.object({
|
|
1466
|
+
scope: jsonPointerSchema,
|
|
1467
|
+
schema: ruleConditionSchema
|
|
1468
|
+
}).strict();
|
|
1469
|
+
var ruleSchema = import_zod.z.object({
|
|
1470
|
+
effect: ruleEffectSchema,
|
|
1471
|
+
condition: schemaBasedConditionSchema
|
|
1472
|
+
}).strict();
|
|
1473
|
+
var uiSchemaElementSchema = import_zod.z.lazy(
|
|
1474
|
+
() => import_zod.z.union([
|
|
1475
|
+
controlSchema,
|
|
1476
|
+
verticalLayoutSchema,
|
|
1477
|
+
horizontalLayoutSchema,
|
|
1478
|
+
groupLayoutSchema,
|
|
1479
|
+
categorizationSchema,
|
|
1480
|
+
categorySchema,
|
|
1481
|
+
labelElementSchema
|
|
1482
|
+
])
|
|
1483
|
+
);
|
|
1484
|
+
var controlSchema = import_zod.z.object({
|
|
1485
|
+
type: import_zod.z.literal("Control"),
|
|
1486
|
+
scope: jsonPointerSchema,
|
|
1487
|
+
label: import_zod.z.union([import_zod.z.string(), import_zod.z.literal(false)]).optional(),
|
|
1488
|
+
rule: ruleSchema.optional(),
|
|
1489
|
+
options: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
|
|
1490
|
+
}).passthrough();
|
|
1491
|
+
var verticalLayoutSchema = import_zod.z.lazy(
|
|
1492
|
+
() => import_zod.z.object({
|
|
1493
|
+
type: import_zod.z.literal("VerticalLayout"),
|
|
1494
|
+
elements: import_zod.z.array(uiSchemaElementSchema),
|
|
1495
|
+
rule: ruleSchema.optional(),
|
|
1496
|
+
options: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
|
|
1497
|
+
}).passthrough()
|
|
1498
|
+
);
|
|
1499
|
+
var horizontalLayoutSchema = import_zod.z.lazy(
|
|
1500
|
+
() => import_zod.z.object({
|
|
1501
|
+
type: import_zod.z.literal("HorizontalLayout"),
|
|
1502
|
+
elements: import_zod.z.array(uiSchemaElementSchema),
|
|
1503
|
+
rule: ruleSchema.optional(),
|
|
1504
|
+
options: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
|
|
1505
|
+
}).passthrough()
|
|
1506
|
+
);
|
|
1507
|
+
var groupLayoutSchema = import_zod.z.lazy(
|
|
1508
|
+
() => import_zod.z.object({
|
|
1509
|
+
type: import_zod.z.literal("Group"),
|
|
1510
|
+
label: import_zod.z.string(),
|
|
1511
|
+
elements: import_zod.z.array(uiSchemaElementSchema),
|
|
1512
|
+
rule: ruleSchema.optional(),
|
|
1513
|
+
options: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
|
|
1514
|
+
}).passthrough()
|
|
1515
|
+
);
|
|
1516
|
+
var categorySchema = import_zod.z.lazy(
|
|
1517
|
+
() => import_zod.z.object({
|
|
1518
|
+
type: import_zod.z.literal("Category"),
|
|
1519
|
+
label: import_zod.z.string(),
|
|
1520
|
+
elements: import_zod.z.array(uiSchemaElementSchema),
|
|
1521
|
+
rule: ruleSchema.optional(),
|
|
1522
|
+
options: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
|
|
1523
|
+
}).passthrough()
|
|
1524
|
+
);
|
|
1525
|
+
var categorizationSchema = import_zod.z.lazy(
|
|
1526
|
+
() => import_zod.z.object({
|
|
1527
|
+
type: import_zod.z.literal("Categorization"),
|
|
1528
|
+
elements: import_zod.z.array(categorySchema),
|
|
1529
|
+
label: import_zod.z.string().optional(),
|
|
1530
|
+
rule: ruleSchema.optional(),
|
|
1531
|
+
options: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
|
|
1532
|
+
}).passthrough()
|
|
1533
|
+
);
|
|
1534
|
+
var labelElementSchema = import_zod.z.object({
|
|
1535
|
+
type: import_zod.z.literal("Label"),
|
|
1536
|
+
text: import_zod.z.string(),
|
|
1537
|
+
rule: ruleSchema.optional(),
|
|
1538
|
+
options: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
|
|
1539
|
+
}).passthrough();
|
|
1540
|
+
var uiSchema = import_zod.z.lazy(
|
|
1541
|
+
() => import_zod.z.union([verticalLayoutSchema, horizontalLayoutSchema, groupLayoutSchema, categorizationSchema])
|
|
1542
|
+
);
|
|
1543
|
+
|
|
1544
|
+
// src/ui-schema/ir-generator.ts
|
|
1545
|
+
var import_zod2 = require("zod");
|
|
1546
|
+
function parseOrThrow(schema, value, label) {
|
|
1547
|
+
try {
|
|
1548
|
+
return schema.parse(value);
|
|
1549
|
+
} catch (error) {
|
|
1550
|
+
if (error instanceof import_zod2.z.ZodError) {
|
|
1551
|
+
throw new Error(
|
|
1552
|
+
`Generated ${label} failed validation:
|
|
1553
|
+
${error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n")}`
|
|
1554
|
+
);
|
|
1555
|
+
}
|
|
1556
|
+
throw error;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
function fieldToScope(fieldName) {
|
|
1560
|
+
return `#/properties/${fieldName}`;
|
|
1561
|
+
}
|
|
1562
|
+
function createShowRule(fieldName, value) {
|
|
1563
|
+
return {
|
|
1564
|
+
effect: "SHOW",
|
|
1565
|
+
condition: {
|
|
1566
|
+
scope: fieldToScope(fieldName),
|
|
1567
|
+
schema: { const: value }
|
|
1568
|
+
}
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
function combineRules(parentRule, childRule) {
|
|
1572
|
+
const parentCondition = parentRule.condition;
|
|
1573
|
+
const childCondition = childRule.condition;
|
|
1574
|
+
return {
|
|
1575
|
+
effect: "SHOW",
|
|
1576
|
+
condition: {
|
|
1577
|
+
scope: "#",
|
|
1578
|
+
schema: {
|
|
1579
|
+
allOf: [
|
|
1580
|
+
{
|
|
1581
|
+
properties: {
|
|
1582
|
+
[parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
|
|
1583
|
+
}
|
|
1584
|
+
},
|
|
1585
|
+
{
|
|
1586
|
+
properties: {
|
|
1587
|
+
[childCondition.scope.replace("#/properties/", "")]: childCondition.schema
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
]
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
function fieldNodeToControl(field, parentRule) {
|
|
1596
|
+
const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
|
|
1597
|
+
const control = {
|
|
1598
|
+
type: "Control",
|
|
1599
|
+
scope: fieldToScope(field.name),
|
|
1600
|
+
...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
|
|
1601
|
+
...parentRule !== void 0 && { rule: parentRule }
|
|
1602
|
+
};
|
|
1603
|
+
return control;
|
|
1604
|
+
}
|
|
1605
|
+
function groupNodeToLayout(group, parentRule) {
|
|
1606
|
+
return {
|
|
1607
|
+
type: "Group",
|
|
1608
|
+
label: group.label,
|
|
1609
|
+
elements: irElementsToUiSchema(group.elements, parentRule),
|
|
1610
|
+
...parentRule !== void 0 && { rule: parentRule }
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
function irElementsToUiSchema(elements, parentRule) {
|
|
1614
|
+
const result = [];
|
|
1615
|
+
for (const element of elements) {
|
|
1616
|
+
switch (element.kind) {
|
|
1617
|
+
case "field": {
|
|
1618
|
+
result.push(fieldNodeToControl(element, parentRule));
|
|
1619
|
+
break;
|
|
1620
|
+
}
|
|
1621
|
+
case "group": {
|
|
1622
|
+
result.push(groupNodeToLayout(element, parentRule));
|
|
1623
|
+
break;
|
|
1624
|
+
}
|
|
1625
|
+
case "conditional": {
|
|
1626
|
+
const newRule = createShowRule(element.fieldName, element.value);
|
|
1627
|
+
const combinedRule = parentRule !== void 0 ? combineRules(parentRule, newRule) : newRule;
|
|
1628
|
+
const childElements = irElementsToUiSchema(element.elements, combinedRule);
|
|
1629
|
+
result.push(...childElements);
|
|
1630
|
+
break;
|
|
1631
|
+
}
|
|
1632
|
+
default: {
|
|
1633
|
+
const _exhaustive = element;
|
|
1634
|
+
void _exhaustive;
|
|
1635
|
+
throw new Error("Unhandled IR element kind");
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
return result;
|
|
1640
|
+
}
|
|
1641
|
+
function generateUiSchemaFromIR(ir) {
|
|
1642
|
+
const result = {
|
|
1643
|
+
type: "VerticalLayout",
|
|
1644
|
+
elements: irElementsToUiSchema(ir.elements)
|
|
1645
|
+
};
|
|
1646
|
+
return parseOrThrow(uiSchema, result, "UI Schema");
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
// src/generators/class-schema.ts
|
|
1650
|
+
function generateClassSchemas(analysis, source) {
|
|
1651
|
+
const ir = canonicalizeTSDoc(analysis, source);
|
|
1652
|
+
return {
|
|
1653
|
+
jsonSchema: generateJsonSchemaFromIR(ir),
|
|
1654
|
+
uiSchema: generateUiSchemaFromIR(ir)
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
// src/validate/constraint-validator.ts
|
|
1659
|
+
function makeCode(ctx, category, number) {
|
|
1660
|
+
return `${ctx.vendorPrefix}-${category}-${String(number).padStart(3, "0")}`;
|
|
1661
|
+
}
|
|
1662
|
+
function addContradiction(ctx, message, primary, related) {
|
|
1663
|
+
ctx.diagnostics.push({
|
|
1664
|
+
code: makeCode(ctx, "CONTRADICTION", 1),
|
|
1665
|
+
message,
|
|
1666
|
+
severity: "error",
|
|
1667
|
+
primaryLocation: primary,
|
|
1668
|
+
relatedLocations: [related]
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
function addTypeMismatch(ctx, message, primary) {
|
|
1672
|
+
ctx.diagnostics.push({
|
|
1673
|
+
code: makeCode(ctx, "TYPE_MISMATCH", 1),
|
|
1674
|
+
message,
|
|
1675
|
+
severity: "error",
|
|
1676
|
+
primaryLocation: primary,
|
|
1677
|
+
relatedLocations: []
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
function addUnknownExtension(ctx, message, primary) {
|
|
1681
|
+
ctx.diagnostics.push({
|
|
1682
|
+
code: makeCode(ctx, "UNKNOWN_EXTENSION", 1),
|
|
1683
|
+
message,
|
|
1684
|
+
severity: "warning",
|
|
1685
|
+
primaryLocation: primary,
|
|
1686
|
+
relatedLocations: []
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
function findNumeric(constraints, constraintKind) {
|
|
1690
|
+
return constraints.find(
|
|
1691
|
+
(c) => c.constraintKind === constraintKind
|
|
1692
|
+
);
|
|
1693
|
+
}
|
|
1694
|
+
function findLength(constraints, constraintKind) {
|
|
1695
|
+
return constraints.find(
|
|
1696
|
+
(c) => c.constraintKind === constraintKind
|
|
1697
|
+
);
|
|
1698
|
+
}
|
|
1699
|
+
function findAllowedMembers(constraints) {
|
|
1700
|
+
return constraints.filter(
|
|
1701
|
+
(c) => c.constraintKind === "allowedMembers"
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1704
|
+
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
1705
|
+
const min = findNumeric(constraints, "minimum");
|
|
1706
|
+
const max = findNumeric(constraints, "maximum");
|
|
1707
|
+
const exMin = findNumeric(constraints, "exclusiveMinimum");
|
|
1708
|
+
const exMax = findNumeric(constraints, "exclusiveMaximum");
|
|
1709
|
+
if (min !== void 0 && max !== void 0 && min.value > max.value) {
|
|
1710
|
+
addContradiction(
|
|
1711
|
+
ctx,
|
|
1712
|
+
`Field "${fieldName}": minimum (${String(min.value)}) is greater than maximum (${String(max.value)})`,
|
|
1713
|
+
min.provenance,
|
|
1714
|
+
max.provenance
|
|
1715
|
+
);
|
|
1716
|
+
}
|
|
1717
|
+
if (exMin !== void 0 && max !== void 0 && exMin.value >= max.value) {
|
|
1718
|
+
addContradiction(
|
|
1719
|
+
ctx,
|
|
1720
|
+
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to maximum (${String(max.value)})`,
|
|
1721
|
+
exMin.provenance,
|
|
1722
|
+
max.provenance
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
if (min !== void 0 && exMax !== void 0 && min.value >= exMax.value) {
|
|
1726
|
+
addContradiction(
|
|
1727
|
+
ctx,
|
|
1728
|
+
`Field "${fieldName}": minimum (${String(min.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
1729
|
+
min.provenance,
|
|
1730
|
+
exMax.provenance
|
|
1731
|
+
);
|
|
1732
|
+
}
|
|
1733
|
+
if (exMin !== void 0 && exMax !== void 0 && exMin.value >= exMax.value) {
|
|
1734
|
+
addContradiction(
|
|
1735
|
+
ctx,
|
|
1736
|
+
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
1737
|
+
exMin.provenance,
|
|
1738
|
+
exMax.provenance
|
|
1739
|
+
);
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
function checkLengthContradictions(ctx, fieldName, constraints) {
|
|
1743
|
+
const minLen = findLength(constraints, "minLength");
|
|
1744
|
+
const maxLen = findLength(constraints, "maxLength");
|
|
1745
|
+
if (minLen !== void 0 && maxLen !== void 0 && minLen.value > maxLen.value) {
|
|
1746
|
+
addContradiction(
|
|
1747
|
+
ctx,
|
|
1748
|
+
`Field "${fieldName}": minLength (${String(minLen.value)}) is greater than maxLength (${String(maxLen.value)})`,
|
|
1749
|
+
minLen.provenance,
|
|
1750
|
+
maxLen.provenance
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
const minItems = findLength(constraints, "minItems");
|
|
1754
|
+
const maxItems = findLength(constraints, "maxItems");
|
|
1755
|
+
if (minItems !== void 0 && maxItems !== void 0 && minItems.value > maxItems.value) {
|
|
1756
|
+
addContradiction(
|
|
1757
|
+
ctx,
|
|
1758
|
+
`Field "${fieldName}": minItems (${String(minItems.value)}) is greater than maxItems (${String(maxItems.value)})`,
|
|
1759
|
+
minItems.provenance,
|
|
1760
|
+
maxItems.provenance
|
|
1761
|
+
);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
|
|
1765
|
+
const members = findAllowedMembers(constraints);
|
|
1766
|
+
if (members.length < 2) return;
|
|
1767
|
+
const firstSet = new Set(members[0]?.members ?? []);
|
|
1768
|
+
for (let i = 1; i < members.length; i++) {
|
|
1769
|
+
const current = members[i];
|
|
1770
|
+
if (current === void 0) continue;
|
|
1771
|
+
for (const m of firstSet) {
|
|
1772
|
+
if (!current.members.includes(m)) {
|
|
1773
|
+
firstSet.delete(m);
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
if (firstSet.size === 0) {
|
|
1778
|
+
const first = members[0];
|
|
1779
|
+
const second = members[1];
|
|
1780
|
+
if (first !== void 0 && second !== void 0) {
|
|
1781
|
+
addContradiction(
|
|
1782
|
+
ctx,
|
|
1783
|
+
`Field "${fieldName}": allowedMembers constraints have an empty intersection (no valid values remain)`,
|
|
1784
|
+
first.provenance,
|
|
1785
|
+
second.provenance
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
function typeLabel(type) {
|
|
1791
|
+
switch (type.kind) {
|
|
1792
|
+
case "primitive":
|
|
1793
|
+
return type.primitiveKind;
|
|
1794
|
+
case "enum":
|
|
1795
|
+
return "enum";
|
|
1796
|
+
case "array":
|
|
1797
|
+
return "array";
|
|
1798
|
+
case "object":
|
|
1799
|
+
return "object";
|
|
1800
|
+
case "union":
|
|
1801
|
+
return "union";
|
|
1802
|
+
case "reference":
|
|
1803
|
+
return `reference(${type.name})`;
|
|
1804
|
+
case "dynamic":
|
|
1805
|
+
return `dynamic(${type.dynamicKind})`;
|
|
1806
|
+
case "custom":
|
|
1807
|
+
return `custom(${type.typeId})`;
|
|
1808
|
+
default: {
|
|
1809
|
+
const _exhaustive = type;
|
|
1810
|
+
return String(_exhaustive);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
1815
|
+
const isNumber = type.kind === "primitive" && type.primitiveKind === "number";
|
|
1816
|
+
const isString = type.kind === "primitive" && type.primitiveKind === "string";
|
|
1817
|
+
const isArray = type.kind === "array";
|
|
1818
|
+
const isEnum = type.kind === "enum";
|
|
1819
|
+
const label = typeLabel(type);
|
|
1820
|
+
for (const constraint of constraints) {
|
|
1821
|
+
const ck = constraint.constraintKind;
|
|
1822
|
+
switch (ck) {
|
|
1823
|
+
case "minimum":
|
|
1824
|
+
case "maximum":
|
|
1825
|
+
case "exclusiveMinimum":
|
|
1826
|
+
case "exclusiveMaximum":
|
|
1827
|
+
case "multipleOf": {
|
|
1828
|
+
if (!isNumber) {
|
|
1829
|
+
addTypeMismatch(
|
|
1830
|
+
ctx,
|
|
1831
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
|
|
1832
|
+
constraint.provenance
|
|
1833
|
+
);
|
|
1834
|
+
}
|
|
1835
|
+
break;
|
|
1836
|
+
}
|
|
1837
|
+
case "minLength":
|
|
1838
|
+
case "maxLength":
|
|
1839
|
+
case "pattern": {
|
|
1840
|
+
if (!isString) {
|
|
1841
|
+
addTypeMismatch(
|
|
1842
|
+
ctx,
|
|
1843
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on string fields, but field type is "${label}"`,
|
|
1844
|
+
constraint.provenance
|
|
1845
|
+
);
|
|
1846
|
+
}
|
|
1847
|
+
break;
|
|
1848
|
+
}
|
|
1849
|
+
case "minItems":
|
|
1850
|
+
case "maxItems":
|
|
1851
|
+
case "uniqueItems": {
|
|
1852
|
+
if (!isArray) {
|
|
1853
|
+
addTypeMismatch(
|
|
1854
|
+
ctx,
|
|
1855
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
|
|
1856
|
+
constraint.provenance
|
|
1857
|
+
);
|
|
1858
|
+
}
|
|
1859
|
+
break;
|
|
1860
|
+
}
|
|
1861
|
+
case "allowedMembers": {
|
|
1862
|
+
if (!isEnum) {
|
|
1863
|
+
addTypeMismatch(
|
|
1864
|
+
ctx,
|
|
1865
|
+
`Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
|
|
1866
|
+
constraint.provenance
|
|
1867
|
+
);
|
|
1868
|
+
}
|
|
1869
|
+
break;
|
|
1870
|
+
}
|
|
1871
|
+
case "custom": {
|
|
1872
|
+
checkCustomConstraint(ctx, fieldName, type, constraint);
|
|
1873
|
+
break;
|
|
1874
|
+
}
|
|
1875
|
+
default: {
|
|
1876
|
+
const _exhaustive = constraint;
|
|
1877
|
+
throw new Error(
|
|
1878
|
+
`Unhandled constraint kind: ${_exhaustive.constraintKind}`
|
|
1879
|
+
);
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
1885
|
+
if (ctx.extensionRegistry === void 0) return;
|
|
1886
|
+
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
1887
|
+
if (registration === void 0) {
|
|
1888
|
+
addUnknownExtension(
|
|
1889
|
+
ctx,
|
|
1890
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not registered in the extension registry`,
|
|
1891
|
+
constraint.provenance
|
|
1892
|
+
);
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
if (registration.applicableTypes === null) return;
|
|
1896
|
+
if (!registration.applicableTypes.includes(type.kind)) {
|
|
1897
|
+
addTypeMismatch(
|
|
1898
|
+
ctx,
|
|
1899
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
1900
|
+
constraint.provenance
|
|
1901
|
+
);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
function validateFieldNode(ctx, field) {
|
|
1905
|
+
validateConstraints(ctx, field.name, field.type, field.constraints);
|
|
1906
|
+
if (field.type.kind === "object") {
|
|
1907
|
+
for (const prop of field.type.properties) {
|
|
1908
|
+
validateObjectProperty(ctx, field.name, prop);
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
function validateObjectProperty(ctx, parentName, prop) {
|
|
1913
|
+
const qualifiedName = `${parentName}.${prop.name}`;
|
|
1914
|
+
validateConstraints(ctx, qualifiedName, prop.type, prop.constraints);
|
|
1915
|
+
if (prop.type.kind === "object") {
|
|
1916
|
+
for (const nestedProp of prop.type.properties) {
|
|
1917
|
+
validateObjectProperty(ctx, qualifiedName, nestedProp);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
function validateConstraints(ctx, name, type, constraints) {
|
|
1922
|
+
checkNumericContradictions(ctx, name, constraints);
|
|
1923
|
+
checkLengthContradictions(ctx, name, constraints);
|
|
1924
|
+
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
1925
|
+
checkTypeApplicability(ctx, name, type, constraints);
|
|
1926
|
+
}
|
|
1927
|
+
function validateElement(ctx, element) {
|
|
1928
|
+
switch (element.kind) {
|
|
1929
|
+
case "field":
|
|
1930
|
+
validateFieldNode(ctx, element);
|
|
1931
|
+
break;
|
|
1932
|
+
case "group":
|
|
1933
|
+
for (const child of element.elements) {
|
|
1934
|
+
validateElement(ctx, child);
|
|
1935
|
+
}
|
|
1936
|
+
break;
|
|
1937
|
+
case "conditional":
|
|
1938
|
+
for (const child of element.elements) {
|
|
1939
|
+
validateElement(ctx, child);
|
|
1940
|
+
}
|
|
1941
|
+
break;
|
|
1942
|
+
default: {
|
|
1943
|
+
const _exhaustive = element;
|
|
1944
|
+
throw new Error(`Unhandled element kind: ${_exhaustive.kind}`);
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
function validateIR(ir, options) {
|
|
1949
|
+
const ctx = {
|
|
1950
|
+
diagnostics: [],
|
|
1951
|
+
vendorPrefix: options?.vendorPrefix ?? "FORMSPEC",
|
|
1952
|
+
extensionRegistry: options?.extensionRegistry
|
|
1953
|
+
};
|
|
1954
|
+
for (const element of ir.elements) {
|
|
1955
|
+
validateElement(ctx, element);
|
|
1956
|
+
}
|
|
1957
|
+
return {
|
|
1958
|
+
diagnostics: ctx.diagnostics,
|
|
1959
|
+
valid: ctx.diagnostics.every((d) => d.severity !== "error")
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
// src/extensions/registry.ts
|
|
1964
|
+
function createExtensionRegistry(extensions) {
|
|
1965
|
+
const typeMap = /* @__PURE__ */ new Map();
|
|
1966
|
+
const constraintMap = /* @__PURE__ */ new Map();
|
|
1967
|
+
const annotationMap = /* @__PURE__ */ new Map();
|
|
1968
|
+
for (const ext of extensions) {
|
|
1969
|
+
if (ext.types !== void 0) {
|
|
1970
|
+
for (const type of ext.types) {
|
|
1971
|
+
const qualifiedId = `${ext.extensionId}/${type.typeName}`;
|
|
1972
|
+
if (typeMap.has(qualifiedId)) {
|
|
1973
|
+
throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
|
|
1974
|
+
}
|
|
1975
|
+
typeMap.set(qualifiedId, type);
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
if (ext.constraints !== void 0) {
|
|
1979
|
+
for (const constraint of ext.constraints) {
|
|
1980
|
+
const qualifiedId = `${ext.extensionId}/${constraint.constraintName}`;
|
|
1981
|
+
if (constraintMap.has(qualifiedId)) {
|
|
1982
|
+
throw new Error(`Duplicate custom constraint ID: "${qualifiedId}"`);
|
|
1983
|
+
}
|
|
1984
|
+
constraintMap.set(qualifiedId, constraint);
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
if (ext.annotations !== void 0) {
|
|
1988
|
+
for (const annotation of ext.annotations) {
|
|
1989
|
+
const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
|
|
1990
|
+
if (annotationMap.has(qualifiedId)) {
|
|
1991
|
+
throw new Error(`Duplicate custom annotation ID: "${qualifiedId}"`);
|
|
1992
|
+
}
|
|
1993
|
+
annotationMap.set(qualifiedId, annotation);
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
return {
|
|
1998
|
+
extensions,
|
|
1999
|
+
findType: (typeId) => typeMap.get(typeId),
|
|
2000
|
+
findConstraint: (constraintId) => constraintMap.get(constraintId),
|
|
2001
|
+
findAnnotation: (annotationId) => annotationMap.get(annotationId)
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
// src/generators/method-schema.ts
|
|
2006
|
+
var import_core5 = require("@formspec/core");
|
|
2007
|
+
function typeToJsonSchema(type, checker) {
|
|
2008
|
+
const typeRegistry = {};
|
|
2009
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
2010
|
+
const typeNode = resolveTypeNode(type, checker, "", typeRegistry, visiting);
|
|
2011
|
+
const fieldProvenance = { surface: "tsdoc", file: "", line: 0, column: 0 };
|
|
2012
|
+
const ir = {
|
|
2013
|
+
kind: "form-ir",
|
|
2014
|
+
irVersion: import_core5.IR_VERSION,
|
|
2015
|
+
elements: [
|
|
2016
|
+
{
|
|
2017
|
+
kind: "field",
|
|
2018
|
+
name: "__result",
|
|
2019
|
+
type: typeNode,
|
|
2020
|
+
required: true,
|
|
2021
|
+
constraints: [],
|
|
2022
|
+
annotations: [],
|
|
2023
|
+
provenance: fieldProvenance
|
|
2024
|
+
}
|
|
2025
|
+
],
|
|
2026
|
+
typeRegistry,
|
|
2027
|
+
provenance: fieldProvenance
|
|
2028
|
+
};
|
|
2029
|
+
const schema = generateJsonSchemaFromIR(ir);
|
|
2030
|
+
const fieldSchema = schema.properties?.["__result"];
|
|
2031
|
+
if (fieldSchema) {
|
|
2032
|
+
if (schema.$defs && Object.keys(schema.$defs).length > 0) {
|
|
2033
|
+
return { ...fieldSchema, $defs: schema.$defs };
|
|
2034
|
+
}
|
|
2035
|
+
return fieldSchema;
|
|
2036
|
+
}
|
|
2037
|
+
return { type: "object" };
|
|
2038
|
+
}
|
|
2039
|
+
function generateMethodSchemas(method, checker, loadedFormSpecs) {
|
|
2040
|
+
const returnType = typeToJsonSchema(method.returnType, checker);
|
|
2041
|
+
const params = generateParamsSchemas(method.parameters, checker, loadedFormSpecs);
|
|
2042
|
+
return {
|
|
2043
|
+
name: method.name,
|
|
2044
|
+
params,
|
|
2045
|
+
returnType
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
function generateParamsSchemas(parameters, checker, loadedFormSpecs) {
|
|
2049
|
+
if (parameters.length === 0) {
|
|
2050
|
+
return null;
|
|
2051
|
+
}
|
|
2052
|
+
for (const param of parameters) {
|
|
2053
|
+
if (param.formSpecExportName) {
|
|
2054
|
+
const formSpec = loadedFormSpecs.get(param.formSpecExportName);
|
|
2055
|
+
if (formSpec) {
|
|
2056
|
+
return {
|
|
2057
|
+
jsonSchema: formSpec.jsonSchema,
|
|
2058
|
+
uiSchema: formSpec.uiSchema,
|
|
2059
|
+
formSpecExport: param.formSpecExportName
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
console.warn(
|
|
2063
|
+
`Warning: FormSpec export "${param.formSpecExportName}" not found, using static analysis`
|
|
2064
|
+
);
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
if (parameters.length === 1 && parameters[0]) {
|
|
2068
|
+
const param = parameters[0];
|
|
2069
|
+
const jsonSchema = typeToJsonSchema(param.type, checker);
|
|
2070
|
+
return {
|
|
2071
|
+
jsonSchema,
|
|
2072
|
+
uiSchema: null,
|
|
2073
|
+
formSpecExport: null
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
2076
|
+
const properties = {};
|
|
2077
|
+
const required = [];
|
|
2078
|
+
for (const param of parameters) {
|
|
2079
|
+
const paramSchema = typeToJsonSchema(param.type, checker);
|
|
2080
|
+
properties[param.name] = paramSchema;
|
|
2081
|
+
if (!param.optional) {
|
|
2082
|
+
required.push(param.name);
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
return {
|
|
2086
|
+
jsonSchema: {
|
|
2087
|
+
type: "object",
|
|
2088
|
+
properties,
|
|
2089
|
+
...required.length > 0 ? { required } : {}
|
|
2090
|
+
},
|
|
2091
|
+
uiSchema: null,
|
|
2092
|
+
formSpecExport: null
|
|
2093
|
+
};
|
|
2094
|
+
}
|
|
2095
|
+
function collectFormSpecReferences(methods) {
|
|
2096
|
+
const references = /* @__PURE__ */ new Set();
|
|
2097
|
+
for (const method of methods) {
|
|
2098
|
+
for (const param of method.parameters) {
|
|
2099
|
+
if (param.formSpecExportName) {
|
|
2100
|
+
references.add(param.formSpecExportName);
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
return references;
|
|
2105
|
+
}
|
|
2106
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2107
|
+
0 && (module.exports = {
|
|
2108
|
+
analyzeClassToIR,
|
|
2109
|
+
analyzeInterfaceToIR,
|
|
2110
|
+
analyzeTypeAliasToIR,
|
|
2111
|
+
canonicalizeChainDSL,
|
|
2112
|
+
canonicalizeTSDoc,
|
|
2113
|
+
collectFormSpecReferences,
|
|
2114
|
+
createExtensionRegistry,
|
|
2115
|
+
createProgramContext,
|
|
2116
|
+
findClassByName,
|
|
2117
|
+
findInterfaceByName,
|
|
2118
|
+
findTypeAliasByName,
|
|
2119
|
+
generateClassSchemas,
|
|
2120
|
+
generateJsonSchemaFromIR,
|
|
2121
|
+
generateMethodSchemas,
|
|
2122
|
+
generateUiSchemaFromIR,
|
|
2123
|
+
validateIR
|
|
2124
|
+
});
|
|
2125
|
+
//# sourceMappingURL=internals.cjs.map
|