@formspec/build 0.1.0-alpha.2 → 0.1.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -0
- package/dist/__tests__/alias-chain-propagation.test.d.ts +9 -0
- package/dist/__tests__/alias-chain-propagation.test.d.ts.map +1 -0
- 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__/class-schema.test.d.ts +2 -0
- package/dist/__tests__/class-schema.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__/date-extension.integration.test.d.ts +2 -0
- package/dist/__tests__/date-extension.integration.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__/extension-runtime.integration.test.d.ts +2 -0
- package/dist/__tests__/extension-runtime.integration.test.d.ts.map +1 -0
- package/dist/__tests__/fixtures/alias-chains.d.ts +37 -0
- package/dist/__tests__/fixtures/alias-chains.d.ts.map +1 -0
- package/dist/__tests__/fixtures/class-schema-regressions.d.ts +87 -0
- package/dist/__tests__/fixtures/class-schema-regressions.d.ts.map +1 -0
- package/dist/__tests__/fixtures/edge-cases.d.ts +132 -0
- package/dist/__tests__/fixtures/edge-cases.d.ts.map +1 -0
- package/dist/__tests__/fixtures/example-a-builtins.d.ts +30 -0
- package/dist/__tests__/fixtures/example-a-builtins.d.ts.map +1 -0
- package/dist/__tests__/fixtures/example-date-extension.d.ts +12 -0
- package/dist/__tests__/fixtures/example-date-extension.d.ts.map +1 -0
- package/dist/__tests__/fixtures/example-interface-types.d.ts +102 -0
- package/dist/__tests__/fixtures/example-interface-types.d.ts.map +1 -0
- package/dist/__tests__/fixtures/example-numeric-extension.d.ts +20 -0
- package/dist/__tests__/fixtures/example-numeric-extension.d.ts.map +1 -0
- package/dist/__tests__/fixtures/extension-forms.d.ts +7 -0
- package/dist/__tests__/fixtures/extension-forms.d.ts.map +1 -0
- package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts +31 -0
- package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts.map +1 -0
- package/dist/__tests__/fixtures/named-primitive-aliases.d.ts +15 -0
- package/dist/__tests__/fixtures/named-primitive-aliases.d.ts.map +1 -0
- package/dist/__tests__/fixtures/nested-array-path-constraints.d.ts +14 -0
- package/dist/__tests__/fixtures/nested-array-path-constraints.d.ts.map +1 -0
- package/dist/__tests__/fixtures/sample-forms.d.ts +65 -0
- package/dist/__tests__/fixtures/sample-forms.d.ts.map +1 -0
- package/dist/__tests__/generate-schemas.test.d.ts +2 -0
- package/dist/__tests__/generate-schemas.test.d.ts.map +1 -0
- package/dist/__tests__/guards.test.d.ts +2 -0
- package/dist/__tests__/guards.test.d.ts.map +1 -0
- 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__/mixed-authoring.test.d.ts +2 -0
- package/dist/__tests__/mixed-authoring.test.d.ts.map +1 -0
- package/dist/__tests__/numeric-extension.integration.test.d.ts +2 -0
- package/dist/__tests__/numeric-extension.integration.test.d.ts.map +1 -0
- package/dist/__tests__/parity/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/plan-status/chain-dsl.d.ts +19 -0
- package/dist/__tests__/parity/fixtures/plan-status/chain-dsl.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/plan-status/expected-ir.d.ts +6 -0
- package/dist/__tests__/parity/fixtures/plan-status/expected-ir.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/plan-status/tsdoc.d.ts +17 -0
- package/dist/__tests__/parity/fixtures/plan-status/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/usd-cents/chain-dsl.d.ts +9 -0
- package/dist/__tests__/parity/fixtures/usd-cents/chain-dsl.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/usd-cents/expected-ir.d.ts +6 -0
- package/dist/__tests__/parity/fixtures/usd-cents/expected-ir.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/usd-cents/tsdoc.d.ts +19 -0
- package/dist/__tests__/parity/fixtures/usd-cents/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 +18 -0
- package/dist/__tests__/parity/parity.test.d.ts.map +1 -0
- package/dist/__tests__/parity/utils.d.ts +151 -0
- package/dist/__tests__/parity/utils.d.ts.map +1 -0
- package/dist/__tests__/path-target-parser.test.d.ts +9 -0
- package/dist/__tests__/path-target-parser.test.d.ts.map +1 -0
- package/dist/analyzer/class-analyzer.d.ts +100 -0
- package/dist/analyzer/class-analyzer.d.ts.map +1 -0
- package/dist/analyzer/jsdoc-constraints.d.ts +53 -0
- package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -0
- package/dist/analyzer/program.d.ts +68 -0
- package/dist/analyzer/program.d.ts.map +1 -0
- package/dist/analyzer/tsdoc-parser.d.ts +122 -0
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -0
- package/dist/browser.cjs +1291 -0
- package/dist/browser.cjs.map +1 -0
- package/dist/browser.d.ts +73 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +1242 -0
- package/dist/browser.js.map +1 -0
- package/dist/build.d.ts +996 -0
- 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 +3862 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.js +3826 -103
- 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 +79 -0
- package/dist/extensions/registry.d.ts.map +1 -0
- package/dist/generators/class-schema.d.ts +99 -0
- package/dist/generators/class-schema.d.ts.map +1 -0
- package/dist/generators/method-schema.d.ts +65 -0
- package/dist/generators/method-schema.d.ts.map +1 -0
- package/dist/generators/mixed-authoring.d.ts +45 -0
- package/dist/generators/mixed-authoring.d.ts.map +1 -0
- package/dist/index.cjs +3627 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +33 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3587 -106
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +3371 -0
- package/dist/internals.cjs.map +1 -0
- package/dist/internals.d.ts +30 -0
- package/dist/internals.d.ts.map +1 -0
- package/dist/internals.js +3345 -0
- package/dist/internals.js.map +1 -0
- package/dist/json-schema/generator.d.ts +16 -5
- package/dist/json-schema/generator.d.ts.map +1 -1
- package/dist/json-schema/ir-generator.d.ts +108 -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 +29 -2
- 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 +23 -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 +27 -7
- package/dist/__tests__/cli.test.js +0 -178
- package/dist/__tests__/cli.test.js.map +0 -1
- package/dist/__tests__/edge-cases.test.js +0 -217
- package/dist/__tests__/edge-cases.test.js.map +0 -1
- package/dist/__tests__/generator.test.js +0 -225
- package/dist/__tests__/generator.test.js.map +0 -1
- package/dist/__tests__/integration.test.js +0 -163
- package/dist/__tests__/integration.test.js.map +0 -1
- package/dist/__tests__/write-schemas.test.js +0 -196
- package/dist/__tests__/write-schemas.test.js.map +0 -1
- package/dist/json-schema/generator.js +0 -161
- package/dist/json-schema/generator.js.map +0 -1
- package/dist/json-schema/types.js +0 -7
- package/dist/json-schema/types.js.map +0 -1
- package/dist/ui-schema/generator.js +0 -150
- 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
package/dist/cli.js
CHANGED
|
@@ -1,22 +1,3753 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/canonicalize/chain-dsl-canonicalizer.ts
|
|
13
|
+
import { IR_VERSION } from "@formspec/core";
|
|
14
|
+
function isGroup(el) {
|
|
15
|
+
return el._type === "group";
|
|
16
|
+
}
|
|
17
|
+
function isConditional(el) {
|
|
18
|
+
return el._type === "conditional";
|
|
19
|
+
}
|
|
20
|
+
function isField(el) {
|
|
21
|
+
return el._type === "field";
|
|
22
|
+
}
|
|
23
|
+
function canonicalizeChainDSL(form) {
|
|
24
|
+
return {
|
|
25
|
+
kind: "form-ir",
|
|
26
|
+
irVersion: IR_VERSION,
|
|
27
|
+
elements: canonicalizeElements(form.elements),
|
|
28
|
+
rootAnnotations: [],
|
|
29
|
+
typeRegistry: {},
|
|
30
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function canonicalizeElements(elements) {
|
|
34
|
+
return elements.map(canonicalizeElement);
|
|
35
|
+
}
|
|
36
|
+
function canonicalizeElement(element) {
|
|
37
|
+
if (isField(element)) {
|
|
38
|
+
return canonicalizeField(element);
|
|
39
|
+
}
|
|
40
|
+
if (isGroup(element)) {
|
|
41
|
+
return canonicalizeGroup(element);
|
|
42
|
+
}
|
|
43
|
+
if (isConditional(element)) {
|
|
44
|
+
return canonicalizeConditional(element);
|
|
45
|
+
}
|
|
46
|
+
const _exhaustive = element;
|
|
47
|
+
throw new Error(`Unknown element type: ${JSON.stringify(_exhaustive)}`);
|
|
48
|
+
}
|
|
49
|
+
function canonicalizeField(field) {
|
|
50
|
+
switch (field._field) {
|
|
51
|
+
case "text":
|
|
52
|
+
return canonicalizeTextField(field);
|
|
53
|
+
case "number":
|
|
54
|
+
return canonicalizeNumberField(field);
|
|
55
|
+
case "boolean":
|
|
56
|
+
return canonicalizeBooleanField(field);
|
|
57
|
+
case "enum":
|
|
58
|
+
return canonicalizeStaticEnumField(field);
|
|
59
|
+
case "dynamic_enum":
|
|
60
|
+
return canonicalizeDynamicEnumField(field);
|
|
61
|
+
case "dynamic_schema":
|
|
62
|
+
return canonicalizeDynamicSchemaField(field);
|
|
63
|
+
case "array":
|
|
64
|
+
return canonicalizeArrayField(field);
|
|
65
|
+
case "object":
|
|
66
|
+
return canonicalizeObjectField(field);
|
|
67
|
+
default: {
|
|
68
|
+
const _exhaustive = field;
|
|
69
|
+
throw new Error(`Unknown field type: ${JSON.stringify(_exhaustive)}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function canonicalizeTextField(field) {
|
|
74
|
+
const type = { kind: "primitive", primitiveKind: "string" };
|
|
75
|
+
const constraints = [];
|
|
76
|
+
if (field.minLength !== void 0) {
|
|
77
|
+
const c = {
|
|
78
|
+
kind: "constraint",
|
|
79
|
+
constraintKind: "minLength",
|
|
80
|
+
value: field.minLength,
|
|
81
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
82
|
+
};
|
|
83
|
+
constraints.push(c);
|
|
84
|
+
}
|
|
85
|
+
if (field.maxLength !== void 0) {
|
|
86
|
+
const c = {
|
|
87
|
+
kind: "constraint",
|
|
88
|
+
constraintKind: "maxLength",
|
|
89
|
+
value: field.maxLength,
|
|
90
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
91
|
+
};
|
|
92
|
+
constraints.push(c);
|
|
93
|
+
}
|
|
94
|
+
if (field.pattern !== void 0) {
|
|
95
|
+
const c = {
|
|
96
|
+
kind: "constraint",
|
|
97
|
+
constraintKind: "pattern",
|
|
98
|
+
pattern: field.pattern,
|
|
99
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
100
|
+
};
|
|
101
|
+
constraints.push(c);
|
|
102
|
+
}
|
|
103
|
+
return buildFieldNode(
|
|
104
|
+
field.name,
|
|
105
|
+
type,
|
|
106
|
+
field.required,
|
|
107
|
+
buildAnnotations(field.label, field.placeholder),
|
|
108
|
+
constraints
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
function canonicalizeNumberField(field) {
|
|
112
|
+
const type = { kind: "primitive", primitiveKind: "number" };
|
|
113
|
+
const constraints = [];
|
|
114
|
+
if (field.min !== void 0) {
|
|
115
|
+
const c = {
|
|
116
|
+
kind: "constraint",
|
|
117
|
+
constraintKind: "minimum",
|
|
118
|
+
value: field.min,
|
|
119
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
120
|
+
};
|
|
121
|
+
constraints.push(c);
|
|
122
|
+
}
|
|
123
|
+
if (field.max !== void 0) {
|
|
124
|
+
const c = {
|
|
125
|
+
kind: "constraint",
|
|
126
|
+
constraintKind: "maximum",
|
|
127
|
+
value: field.max,
|
|
128
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
129
|
+
};
|
|
130
|
+
constraints.push(c);
|
|
131
|
+
}
|
|
132
|
+
if (field.multipleOf !== void 0) {
|
|
133
|
+
const c = {
|
|
134
|
+
kind: "constraint",
|
|
135
|
+
constraintKind: "multipleOf",
|
|
136
|
+
value: field.multipleOf,
|
|
137
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
138
|
+
};
|
|
139
|
+
constraints.push(c);
|
|
140
|
+
}
|
|
141
|
+
return buildFieldNode(
|
|
142
|
+
field.name,
|
|
143
|
+
type,
|
|
144
|
+
field.required,
|
|
145
|
+
buildAnnotations(field.label),
|
|
146
|
+
constraints
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
function canonicalizeBooleanField(field) {
|
|
150
|
+
const type = { kind: "primitive", primitiveKind: "boolean" };
|
|
151
|
+
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
152
|
+
}
|
|
153
|
+
function canonicalizeStaticEnumField(field) {
|
|
154
|
+
const members = field.options.map((opt) => {
|
|
155
|
+
if (typeof opt === "string") {
|
|
156
|
+
return { value: opt };
|
|
157
|
+
}
|
|
158
|
+
return { value: opt.id, displayName: opt.label };
|
|
159
|
+
});
|
|
160
|
+
const type = { kind: "enum", members };
|
|
161
|
+
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
162
|
+
}
|
|
163
|
+
function canonicalizeDynamicEnumField(field) {
|
|
164
|
+
const type = {
|
|
165
|
+
kind: "dynamic",
|
|
166
|
+
dynamicKind: "enum",
|
|
167
|
+
sourceKey: field.source,
|
|
168
|
+
parameterFields: field.params ? [...field.params] : []
|
|
169
|
+
};
|
|
170
|
+
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
171
|
+
}
|
|
172
|
+
function canonicalizeDynamicSchemaField(field) {
|
|
173
|
+
const type = {
|
|
174
|
+
kind: "dynamic",
|
|
175
|
+
dynamicKind: "schema",
|
|
176
|
+
sourceKey: field.schemaSource,
|
|
177
|
+
parameterFields: []
|
|
178
|
+
};
|
|
179
|
+
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
180
|
+
}
|
|
181
|
+
function canonicalizeArrayField(field) {
|
|
182
|
+
const itemProperties = buildObjectProperties(field.items);
|
|
183
|
+
const itemsType = {
|
|
184
|
+
kind: "object",
|
|
185
|
+
properties: itemProperties,
|
|
186
|
+
additionalProperties: true
|
|
187
|
+
};
|
|
188
|
+
const type = { kind: "array", items: itemsType };
|
|
189
|
+
const constraints = [];
|
|
190
|
+
if (field.minItems !== void 0) {
|
|
191
|
+
const c = {
|
|
192
|
+
kind: "constraint",
|
|
193
|
+
constraintKind: "minItems",
|
|
194
|
+
value: field.minItems,
|
|
195
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
196
|
+
};
|
|
197
|
+
constraints.push(c);
|
|
198
|
+
}
|
|
199
|
+
if (field.maxItems !== void 0) {
|
|
200
|
+
const c = {
|
|
201
|
+
kind: "constraint",
|
|
202
|
+
constraintKind: "maxItems",
|
|
203
|
+
value: field.maxItems,
|
|
204
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
205
|
+
};
|
|
206
|
+
constraints.push(c);
|
|
207
|
+
}
|
|
208
|
+
return buildFieldNode(
|
|
209
|
+
field.name,
|
|
210
|
+
type,
|
|
211
|
+
field.required,
|
|
212
|
+
buildAnnotations(field.label),
|
|
213
|
+
constraints
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
function canonicalizeObjectField(field) {
|
|
217
|
+
const properties = buildObjectProperties(field.properties);
|
|
218
|
+
const type = {
|
|
219
|
+
kind: "object",
|
|
220
|
+
properties,
|
|
221
|
+
additionalProperties: true
|
|
222
|
+
};
|
|
223
|
+
return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
|
|
224
|
+
}
|
|
225
|
+
function canonicalizeGroup(g) {
|
|
226
|
+
return {
|
|
227
|
+
kind: "group",
|
|
228
|
+
label: g.label,
|
|
229
|
+
elements: canonicalizeElements(g.elements),
|
|
230
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function canonicalizeConditional(c) {
|
|
234
|
+
return {
|
|
235
|
+
kind: "conditional",
|
|
236
|
+
fieldName: c.field,
|
|
237
|
+
// Conditional values from the chain DSL are JSON-serializable primitives
|
|
238
|
+
// (strings, numbers, booleans) produced by the `is()` predicate helper.
|
|
239
|
+
value: assertJsonValue(c.value),
|
|
240
|
+
elements: canonicalizeElements(c.elements),
|
|
241
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function assertJsonValue(v) {
|
|
245
|
+
if (v === null || typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
246
|
+
return v;
|
|
247
|
+
}
|
|
248
|
+
if (Array.isArray(v)) {
|
|
249
|
+
return v.map(assertJsonValue);
|
|
250
|
+
}
|
|
251
|
+
if (typeof v === "object") {
|
|
252
|
+
const result = {};
|
|
253
|
+
for (const [key, val] of Object.entries(v)) {
|
|
254
|
+
result[key] = assertJsonValue(val);
|
|
255
|
+
}
|
|
256
|
+
return result;
|
|
257
|
+
}
|
|
258
|
+
throw new TypeError(`Conditional value is not a valid JsonValue: ${typeof v}`);
|
|
259
|
+
}
|
|
260
|
+
function buildFieldNode(name, type, required, annotations, constraints = []) {
|
|
261
|
+
return {
|
|
262
|
+
kind: "field",
|
|
263
|
+
name,
|
|
264
|
+
type,
|
|
265
|
+
required: required === true,
|
|
266
|
+
constraints,
|
|
267
|
+
annotations,
|
|
268
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
function buildAnnotations(label, placeholder) {
|
|
272
|
+
const annotations = [];
|
|
273
|
+
if (label !== void 0) {
|
|
274
|
+
const a = {
|
|
275
|
+
kind: "annotation",
|
|
276
|
+
annotationKind: "displayName",
|
|
277
|
+
value: label,
|
|
278
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
279
|
+
};
|
|
280
|
+
annotations.push(a);
|
|
281
|
+
}
|
|
282
|
+
if (placeholder !== void 0) {
|
|
283
|
+
const a = {
|
|
284
|
+
kind: "annotation",
|
|
285
|
+
annotationKind: "placeholder",
|
|
286
|
+
value: placeholder,
|
|
287
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
288
|
+
};
|
|
289
|
+
annotations.push(a);
|
|
290
|
+
}
|
|
291
|
+
return annotations;
|
|
292
|
+
}
|
|
293
|
+
function buildObjectProperties(elements, insideConditional = false) {
|
|
294
|
+
const properties = [];
|
|
295
|
+
for (const el of elements) {
|
|
296
|
+
if (isField(el)) {
|
|
297
|
+
const fieldNode = canonicalizeField(el);
|
|
298
|
+
properties.push({
|
|
299
|
+
name: fieldNode.name,
|
|
300
|
+
type: fieldNode.type,
|
|
301
|
+
// Fields inside a conditional branch are always optional in the
|
|
302
|
+
// data schema, regardless of their `required` flag — the condition
|
|
303
|
+
// may not be met, so the field may be absent.
|
|
304
|
+
optional: insideConditional || !fieldNode.required,
|
|
305
|
+
constraints: fieldNode.constraints,
|
|
306
|
+
annotations: fieldNode.annotations,
|
|
307
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
308
|
+
});
|
|
309
|
+
} else if (isGroup(el)) {
|
|
310
|
+
properties.push(...buildObjectProperties(el.elements, insideConditional));
|
|
311
|
+
} else if (isConditional(el)) {
|
|
312
|
+
properties.push(...buildObjectProperties(el.elements, true));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return properties;
|
|
316
|
+
}
|
|
317
|
+
var CHAIN_DSL_PROVENANCE;
|
|
318
|
+
var init_chain_dsl_canonicalizer = __esm({
|
|
319
|
+
"src/canonicalize/chain-dsl-canonicalizer.ts"() {
|
|
320
|
+
"use strict";
|
|
321
|
+
CHAIN_DSL_PROVENANCE = {
|
|
322
|
+
surface: "chain-dsl",
|
|
323
|
+
file: "",
|
|
324
|
+
line: 0,
|
|
325
|
+
column: 0
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// src/canonicalize/tsdoc-canonicalizer.ts
|
|
331
|
+
import { IR_VERSION as IR_VERSION2 } from "@formspec/core";
|
|
332
|
+
function canonicalizeTSDoc(analysis, source) {
|
|
333
|
+
const file = source?.file ?? "";
|
|
334
|
+
const provenance = {
|
|
335
|
+
surface: "tsdoc",
|
|
336
|
+
file,
|
|
337
|
+
line: 1,
|
|
338
|
+
column: 0
|
|
339
|
+
};
|
|
340
|
+
const elements = assembleElements(analysis.fields, analysis.fieldLayouts, provenance);
|
|
341
|
+
return {
|
|
342
|
+
kind: "form-ir",
|
|
343
|
+
irVersion: IR_VERSION2,
|
|
344
|
+
elements,
|
|
345
|
+
typeRegistry: analysis.typeRegistry,
|
|
346
|
+
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { rootAnnotations: analysis.annotations },
|
|
347
|
+
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
|
|
348
|
+
provenance
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
function assembleElements(fields, layouts, provenance) {
|
|
352
|
+
const elements = [];
|
|
353
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
354
|
+
const topLevelOrder = [];
|
|
355
|
+
for (let i = 0; i < fields.length; i++) {
|
|
356
|
+
const field = fields[i];
|
|
357
|
+
const layout = layouts[i];
|
|
358
|
+
if (!field || !layout) continue;
|
|
359
|
+
const element = wrapInConditional(field, layout, provenance);
|
|
360
|
+
if (layout.groupLabel !== void 0) {
|
|
361
|
+
const label = layout.groupLabel;
|
|
362
|
+
let groupElements = groupMap.get(label);
|
|
363
|
+
if (!groupElements) {
|
|
364
|
+
groupElements = [];
|
|
365
|
+
groupMap.set(label, groupElements);
|
|
366
|
+
topLevelOrder.push({ type: "group", label });
|
|
367
|
+
}
|
|
368
|
+
groupElements.push(element);
|
|
369
|
+
} else {
|
|
370
|
+
topLevelOrder.push({ type: "element", element });
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
for (const entry of topLevelOrder) {
|
|
374
|
+
if (entry.type === "group") {
|
|
375
|
+
const groupElements = groupMap.get(entry.label);
|
|
376
|
+
if (groupElements) {
|
|
377
|
+
const groupNode = {
|
|
378
|
+
kind: "group",
|
|
379
|
+
label: entry.label,
|
|
380
|
+
elements: groupElements,
|
|
381
|
+
provenance
|
|
382
|
+
};
|
|
383
|
+
elements.push(groupNode);
|
|
384
|
+
groupMap.delete(entry.label);
|
|
385
|
+
}
|
|
386
|
+
} else {
|
|
387
|
+
elements.push(entry.element);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return elements;
|
|
391
|
+
}
|
|
392
|
+
function wrapInConditional(field, layout, provenance) {
|
|
393
|
+
if (layout.showWhen === void 0) {
|
|
394
|
+
return field;
|
|
395
|
+
}
|
|
396
|
+
const conditional = {
|
|
397
|
+
kind: "conditional",
|
|
398
|
+
fieldName: layout.showWhen.field,
|
|
399
|
+
value: layout.showWhen.value,
|
|
400
|
+
elements: [field],
|
|
401
|
+
provenance
|
|
402
|
+
};
|
|
403
|
+
return conditional;
|
|
404
|
+
}
|
|
405
|
+
var init_tsdoc_canonicalizer = __esm({
|
|
406
|
+
"src/canonicalize/tsdoc-canonicalizer.ts"() {
|
|
407
|
+
"use strict";
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// src/canonicalize/index.ts
|
|
412
|
+
var init_canonicalize = __esm({
|
|
413
|
+
"src/canonicalize/index.ts"() {
|
|
414
|
+
"use strict";
|
|
415
|
+
init_chain_dsl_canonicalizer();
|
|
416
|
+
init_tsdoc_canonicalizer();
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// src/json-schema/ir-generator.ts
|
|
421
|
+
function makeContext(options) {
|
|
422
|
+
const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
|
|
423
|
+
if (!vendorPrefix.startsWith("x-")) {
|
|
424
|
+
throw new Error(
|
|
425
|
+
`Invalid vendorPrefix "${vendorPrefix}". Extension JSON Schema keywords must start with "x-".`
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
return {
|
|
429
|
+
defs: {},
|
|
430
|
+
extensionRegistry: options?.extensionRegistry,
|
|
431
|
+
vendorPrefix
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
function generateJsonSchemaFromIR(ir, options) {
|
|
435
|
+
const ctx = makeContext(options);
|
|
436
|
+
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
437
|
+
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
438
|
+
if (typeDef.constraints && typeDef.constraints.length > 0) {
|
|
439
|
+
applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
|
|
440
|
+
}
|
|
441
|
+
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
442
|
+
applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
const properties = {};
|
|
446
|
+
const required = [];
|
|
447
|
+
collectFields(ir.elements, properties, required, ctx);
|
|
448
|
+
const uniqueRequired = [...new Set(required)];
|
|
449
|
+
const result = {
|
|
450
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
451
|
+
type: "object",
|
|
452
|
+
properties,
|
|
453
|
+
...uniqueRequired.length > 0 && { required: uniqueRequired }
|
|
454
|
+
};
|
|
455
|
+
if (ir.annotations && ir.annotations.length > 0) {
|
|
456
|
+
applyAnnotations(result, ir.annotations, ctx);
|
|
457
|
+
}
|
|
458
|
+
if (Object.keys(ctx.defs).length > 0) {
|
|
459
|
+
result.$defs = ctx.defs;
|
|
460
|
+
}
|
|
461
|
+
return result;
|
|
462
|
+
}
|
|
463
|
+
function collectFields(elements, properties, required, ctx) {
|
|
464
|
+
for (const element of elements) {
|
|
465
|
+
switch (element.kind) {
|
|
466
|
+
case "field":
|
|
467
|
+
properties[element.name] = generateFieldSchema(element, ctx);
|
|
468
|
+
if (element.required) {
|
|
469
|
+
required.push(element.name);
|
|
470
|
+
}
|
|
471
|
+
break;
|
|
472
|
+
case "group":
|
|
473
|
+
collectFields(element.elements, properties, required, ctx);
|
|
474
|
+
break;
|
|
475
|
+
case "conditional":
|
|
476
|
+
collectFields(element.elements, properties, required, ctx);
|
|
477
|
+
break;
|
|
478
|
+
default: {
|
|
479
|
+
const _exhaustive = element;
|
|
480
|
+
void _exhaustive;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
function generateFieldSchema(field, ctx) {
|
|
486
|
+
const schema = generateTypeNode(field.type, ctx);
|
|
487
|
+
const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
|
|
488
|
+
const directConstraints = [];
|
|
489
|
+
const itemConstraints = [];
|
|
490
|
+
const pathConstraints = [];
|
|
491
|
+
for (const c of field.constraints) {
|
|
492
|
+
if (c.path) {
|
|
493
|
+
pathConstraints.push(c);
|
|
494
|
+
} else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
|
|
495
|
+
itemConstraints.push(c);
|
|
496
|
+
} else {
|
|
497
|
+
directConstraints.push(c);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
applyConstraints(schema, directConstraints, ctx);
|
|
501
|
+
if (itemStringSchema !== void 0) {
|
|
502
|
+
applyConstraints(itemStringSchema, itemConstraints, ctx);
|
|
503
|
+
}
|
|
504
|
+
const rootAnnotations = [];
|
|
505
|
+
const itemAnnotations = [];
|
|
506
|
+
for (const annotation of field.annotations) {
|
|
507
|
+
if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
|
|
508
|
+
itemAnnotations.push(annotation);
|
|
509
|
+
} else {
|
|
510
|
+
rootAnnotations.push(annotation);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
applyAnnotations(schema, rootAnnotations, ctx);
|
|
514
|
+
if (itemStringSchema !== void 0) {
|
|
515
|
+
applyAnnotations(itemStringSchema, itemAnnotations, ctx);
|
|
516
|
+
}
|
|
517
|
+
if (pathConstraints.length === 0) {
|
|
518
|
+
return schema;
|
|
519
|
+
}
|
|
520
|
+
return applyPathTargetedConstraints(schema, pathConstraints, ctx);
|
|
521
|
+
}
|
|
522
|
+
function isStringItemConstraint(constraint) {
|
|
523
|
+
switch (constraint.constraintKind) {
|
|
524
|
+
case "minLength":
|
|
525
|
+
case "maxLength":
|
|
526
|
+
case "pattern":
|
|
527
|
+
return true;
|
|
528
|
+
default:
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
533
|
+
if (schema.type === "array" && schema.items) {
|
|
534
|
+
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
|
|
535
|
+
return schema;
|
|
536
|
+
}
|
|
537
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
538
|
+
for (const c of pathConstraints) {
|
|
539
|
+
const target = c.path?.segments[0];
|
|
540
|
+
if (!target) continue;
|
|
541
|
+
const group = byTarget.get(target) ?? [];
|
|
542
|
+
group.push(c);
|
|
543
|
+
byTarget.set(target, group);
|
|
544
|
+
}
|
|
545
|
+
const propertyOverrides = {};
|
|
546
|
+
for (const [target, constraints] of byTarget) {
|
|
547
|
+
const subSchema = {};
|
|
548
|
+
applyConstraints(subSchema, constraints, ctx);
|
|
549
|
+
propertyOverrides[target] = subSchema;
|
|
550
|
+
}
|
|
551
|
+
if (schema.$ref) {
|
|
552
|
+
const { $ref, ...rest } = schema;
|
|
553
|
+
const refPart = { $ref };
|
|
554
|
+
const overridePart = {
|
|
555
|
+
properties: propertyOverrides,
|
|
556
|
+
...rest
|
|
557
|
+
};
|
|
558
|
+
return { allOf: [refPart, overridePart] };
|
|
559
|
+
}
|
|
560
|
+
if (schema.type === "object" && schema.properties) {
|
|
561
|
+
const missingOverrides = {};
|
|
562
|
+
for (const [target, overrideSchema] of Object.entries(propertyOverrides)) {
|
|
563
|
+
if (schema.properties[target]) {
|
|
564
|
+
Object.assign(schema.properties[target], overrideSchema);
|
|
565
|
+
} else {
|
|
566
|
+
missingOverrides[target] = overrideSchema;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
if (Object.keys(missingOverrides).length === 0) {
|
|
570
|
+
return schema;
|
|
571
|
+
}
|
|
572
|
+
return {
|
|
573
|
+
allOf: [schema, { properties: missingOverrides }]
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
if (schema.allOf) {
|
|
577
|
+
schema.allOf = [...schema.allOf, { properties: propertyOverrides }];
|
|
578
|
+
return schema;
|
|
579
|
+
}
|
|
580
|
+
return schema;
|
|
581
|
+
}
|
|
582
|
+
function generateTypeNode(type, ctx) {
|
|
583
|
+
switch (type.kind) {
|
|
584
|
+
case "primitive":
|
|
585
|
+
return generatePrimitiveType(type);
|
|
586
|
+
case "enum":
|
|
587
|
+
return generateEnumType(type);
|
|
588
|
+
case "array":
|
|
589
|
+
return generateArrayType(type, ctx);
|
|
590
|
+
case "object":
|
|
591
|
+
return generateObjectType(type, ctx);
|
|
592
|
+
case "record":
|
|
593
|
+
return generateRecordType(type, ctx);
|
|
594
|
+
case "union":
|
|
595
|
+
return generateUnionType(type, ctx);
|
|
596
|
+
case "reference":
|
|
597
|
+
return generateReferenceType(type);
|
|
598
|
+
case "dynamic":
|
|
599
|
+
return generateDynamicType(type);
|
|
600
|
+
case "custom":
|
|
601
|
+
return generateCustomType(type, ctx);
|
|
602
|
+
default: {
|
|
603
|
+
const _exhaustive = type;
|
|
604
|
+
return _exhaustive;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
function generatePrimitiveType(type) {
|
|
609
|
+
return {
|
|
610
|
+
type: type.primitiveKind === "integer" || type.primitiveKind === "bigint" ? "integer" : type.primitiveKind
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
function generateEnumType(type) {
|
|
614
|
+
const hasDisplayNames = type.members.some((m) => m.displayName !== void 0);
|
|
615
|
+
if (hasDisplayNames) {
|
|
616
|
+
return {
|
|
617
|
+
oneOf: type.members.map((m) => {
|
|
618
|
+
const entry = { const: m.value };
|
|
619
|
+
if (m.displayName !== void 0) {
|
|
620
|
+
entry.title = m.displayName;
|
|
621
|
+
}
|
|
622
|
+
return entry;
|
|
623
|
+
})
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
return { enum: type.members.map((m) => m.value) };
|
|
627
|
+
}
|
|
628
|
+
function generateArrayType(type, ctx) {
|
|
629
|
+
return {
|
|
630
|
+
type: "array",
|
|
631
|
+
items: generateTypeNode(type.items, ctx)
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
function generateObjectType(type, ctx) {
|
|
635
|
+
const properties = {};
|
|
636
|
+
const required = [];
|
|
637
|
+
for (const prop of type.properties) {
|
|
638
|
+
properties[prop.name] = generatePropertySchema(prop, ctx);
|
|
639
|
+
if (!prop.optional) {
|
|
640
|
+
required.push(prop.name);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
const schema = { type: "object", properties };
|
|
644
|
+
if (required.length > 0) {
|
|
645
|
+
schema.required = required;
|
|
646
|
+
}
|
|
647
|
+
if (!type.additionalProperties) {
|
|
648
|
+
schema.additionalProperties = false;
|
|
649
|
+
}
|
|
650
|
+
return schema;
|
|
651
|
+
}
|
|
652
|
+
function generateRecordType(type, ctx) {
|
|
653
|
+
return {
|
|
654
|
+
type: "object",
|
|
655
|
+
additionalProperties: generateTypeNode(type.valueType, ctx)
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
function generatePropertySchema(prop, ctx) {
|
|
659
|
+
const schema = generateTypeNode(prop.type, ctx);
|
|
660
|
+
applyConstraints(schema, prop.constraints, ctx);
|
|
661
|
+
applyAnnotations(schema, prop.annotations, ctx);
|
|
662
|
+
return schema;
|
|
663
|
+
}
|
|
664
|
+
function generateUnionType(type, ctx) {
|
|
665
|
+
if (isBooleanUnion(type)) {
|
|
666
|
+
return { type: "boolean" };
|
|
667
|
+
}
|
|
668
|
+
if (isNullableUnion(type)) {
|
|
669
|
+
return {
|
|
670
|
+
oneOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
return {
|
|
674
|
+
anyOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
function isBooleanUnion(type) {
|
|
678
|
+
if (type.members.length !== 2) return false;
|
|
679
|
+
const kinds = type.members.map((m) => m.kind);
|
|
680
|
+
return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
|
|
681
|
+
}
|
|
682
|
+
function isNullableUnion(type) {
|
|
683
|
+
if (type.members.length !== 2) return false;
|
|
684
|
+
const nullCount = type.members.filter(
|
|
685
|
+
(m) => m.kind === "primitive" && m.primitiveKind === "null"
|
|
686
|
+
).length;
|
|
687
|
+
return nullCount === 1;
|
|
688
|
+
}
|
|
689
|
+
function generateReferenceType(type) {
|
|
690
|
+
return { $ref: `#/$defs/${type.name}` };
|
|
691
|
+
}
|
|
692
|
+
function generateDynamicType(type) {
|
|
693
|
+
if (type.dynamicKind === "enum") {
|
|
694
|
+
const schema = {
|
|
695
|
+
type: "string",
|
|
696
|
+
"x-formspec-source": type.sourceKey
|
|
697
|
+
};
|
|
698
|
+
if (type.parameterFields.length > 0) {
|
|
699
|
+
schema["x-formspec-params"] = [...type.parameterFields];
|
|
700
|
+
}
|
|
701
|
+
return schema;
|
|
702
|
+
}
|
|
703
|
+
return {
|
|
704
|
+
type: "object",
|
|
705
|
+
additionalProperties: true,
|
|
706
|
+
"x-formspec-schemaSource": type.sourceKey
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
function applyConstraints(schema, constraints, ctx) {
|
|
710
|
+
for (const constraint of constraints) {
|
|
711
|
+
switch (constraint.constraintKind) {
|
|
712
|
+
case "minimum":
|
|
713
|
+
schema.minimum = constraint.value;
|
|
714
|
+
break;
|
|
715
|
+
case "maximum":
|
|
716
|
+
schema.maximum = constraint.value;
|
|
717
|
+
break;
|
|
718
|
+
case "exclusiveMinimum":
|
|
719
|
+
schema.exclusiveMinimum = constraint.value;
|
|
720
|
+
break;
|
|
721
|
+
case "exclusiveMaximum":
|
|
722
|
+
schema.exclusiveMaximum = constraint.value;
|
|
723
|
+
break;
|
|
724
|
+
case "multipleOf": {
|
|
725
|
+
const { value } = constraint;
|
|
726
|
+
if (value === 1 && schema.type === "number") {
|
|
727
|
+
schema.type = "integer";
|
|
728
|
+
} else {
|
|
729
|
+
schema.multipleOf = value;
|
|
730
|
+
}
|
|
731
|
+
break;
|
|
732
|
+
}
|
|
733
|
+
case "minLength":
|
|
734
|
+
schema.minLength = constraint.value;
|
|
735
|
+
break;
|
|
736
|
+
case "maxLength":
|
|
737
|
+
schema.maxLength = constraint.value;
|
|
738
|
+
break;
|
|
739
|
+
case "minItems":
|
|
740
|
+
schema.minItems = constraint.value;
|
|
741
|
+
break;
|
|
742
|
+
case "maxItems":
|
|
743
|
+
schema.maxItems = constraint.value;
|
|
744
|
+
break;
|
|
745
|
+
case "pattern":
|
|
746
|
+
schema.pattern = constraint.pattern;
|
|
747
|
+
break;
|
|
748
|
+
case "uniqueItems":
|
|
749
|
+
schema.uniqueItems = constraint.value;
|
|
750
|
+
break;
|
|
751
|
+
case "const":
|
|
752
|
+
schema.const = constraint.value;
|
|
753
|
+
break;
|
|
754
|
+
case "allowedMembers":
|
|
755
|
+
break;
|
|
756
|
+
case "custom":
|
|
757
|
+
applyCustomConstraint(schema, constraint, ctx);
|
|
758
|
+
break;
|
|
759
|
+
default: {
|
|
760
|
+
const _exhaustive = constraint;
|
|
761
|
+
void _exhaustive;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
function applyAnnotations(schema, annotations, ctx) {
|
|
767
|
+
for (const annotation of annotations) {
|
|
768
|
+
switch (annotation.annotationKind) {
|
|
769
|
+
case "displayName":
|
|
770
|
+
schema.title = annotation.value;
|
|
771
|
+
break;
|
|
772
|
+
case "description":
|
|
773
|
+
schema.description = annotation.value;
|
|
774
|
+
break;
|
|
775
|
+
case "defaultValue":
|
|
776
|
+
schema.default = annotation.value;
|
|
777
|
+
break;
|
|
778
|
+
case "format":
|
|
779
|
+
schema.format = annotation.value;
|
|
780
|
+
break;
|
|
781
|
+
case "deprecated":
|
|
782
|
+
schema.deprecated = true;
|
|
783
|
+
if (annotation.message !== void 0 && annotation.message !== "") {
|
|
784
|
+
schema[`${ctx.vendorPrefix}-deprecation-description`] = annotation.message;
|
|
785
|
+
}
|
|
786
|
+
break;
|
|
787
|
+
case "placeholder":
|
|
788
|
+
break;
|
|
789
|
+
case "formatHint":
|
|
790
|
+
break;
|
|
791
|
+
case "custom":
|
|
792
|
+
applyCustomAnnotation(schema, annotation, ctx);
|
|
793
|
+
break;
|
|
794
|
+
default: {
|
|
795
|
+
const _exhaustive = annotation;
|
|
796
|
+
void _exhaustive;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
function generateCustomType(type, ctx) {
|
|
802
|
+
const registration = ctx.extensionRegistry?.findType(type.typeId);
|
|
803
|
+
if (registration === void 0) {
|
|
804
|
+
throw new Error(
|
|
805
|
+
`Cannot generate JSON Schema for custom type "${type.typeId}" without a matching extension registration`
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
return registration.toJsonSchema(type.payload, ctx.vendorPrefix);
|
|
809
|
+
}
|
|
810
|
+
function applyCustomConstraint(schema, constraint, ctx) {
|
|
811
|
+
const registration = ctx.extensionRegistry?.findConstraint(constraint.constraintId);
|
|
812
|
+
if (registration === void 0) {
|
|
813
|
+
throw new Error(
|
|
814
|
+
`Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
assignVendorPrefixedExtensionKeywords(
|
|
818
|
+
schema,
|
|
819
|
+
registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
|
|
820
|
+
ctx.vendorPrefix,
|
|
821
|
+
`custom constraint "${constraint.constraintId}"`
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
function applyCustomAnnotation(schema, annotation, ctx) {
|
|
825
|
+
const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
|
|
826
|
+
if (registration === void 0) {
|
|
827
|
+
throw new Error(
|
|
828
|
+
`Cannot generate JSON Schema for custom annotation "${annotation.annotationId}" without a matching extension registration`
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
if (registration.toJsonSchema === void 0) {
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
assignVendorPrefixedExtensionKeywords(
|
|
835
|
+
schema,
|
|
836
|
+
registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
|
|
837
|
+
ctx.vendorPrefix,
|
|
838
|
+
`custom annotation "${annotation.annotationId}"`
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
|
|
842
|
+
for (const [key, value] of Object.entries(extensionSchema)) {
|
|
843
|
+
if (!key.startsWith(`${vendorPrefix}-`)) {
|
|
844
|
+
throw new Error(
|
|
845
|
+
`Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
schema[key] = value;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
var init_ir_generator = __esm({
|
|
852
|
+
"src/json-schema/ir-generator.ts"() {
|
|
853
|
+
"use strict";
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
// src/json-schema/generator.ts
|
|
858
|
+
function generateJsonSchema(form, options) {
|
|
859
|
+
const ir = canonicalizeChainDSL(form);
|
|
860
|
+
return generateJsonSchemaFromIR(ir, options);
|
|
861
|
+
}
|
|
862
|
+
var init_generator = __esm({
|
|
863
|
+
"src/json-schema/generator.ts"() {
|
|
864
|
+
"use strict";
|
|
865
|
+
init_canonicalize();
|
|
866
|
+
init_ir_generator();
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
// src/ui-schema/schema.ts
|
|
871
|
+
import { z } from "zod";
|
|
872
|
+
var jsonPointerSchema, ruleEffectSchema, uiSchemaElementTypeSchema, ruleConditionSchema, schemaBasedConditionSchema, ruleSchema, uiSchemaElementSchema, controlSchema, verticalLayoutSchema, horizontalLayoutSchema, groupLayoutSchema, categorySchema, categorizationSchema, labelElementSchema, uiSchema;
|
|
873
|
+
var init_schema = __esm({
|
|
874
|
+
"src/ui-schema/schema.ts"() {
|
|
875
|
+
"use strict";
|
|
876
|
+
jsonPointerSchema = z.string();
|
|
877
|
+
ruleEffectSchema = z.enum(["SHOW", "HIDE", "ENABLE", "DISABLE"]);
|
|
878
|
+
uiSchemaElementTypeSchema = z.enum([
|
|
879
|
+
"Control",
|
|
880
|
+
"VerticalLayout",
|
|
881
|
+
"HorizontalLayout",
|
|
882
|
+
"Group",
|
|
883
|
+
"Categorization",
|
|
884
|
+
"Category",
|
|
885
|
+
"Label"
|
|
886
|
+
]);
|
|
887
|
+
ruleConditionSchema = z.lazy(
|
|
888
|
+
() => z.object({
|
|
889
|
+
const: z.unknown().optional(),
|
|
890
|
+
enum: z.array(z.unknown()).readonly().optional(),
|
|
891
|
+
type: z.string().optional(),
|
|
892
|
+
not: ruleConditionSchema.optional(),
|
|
893
|
+
minimum: z.number().optional(),
|
|
894
|
+
maximum: z.number().optional(),
|
|
895
|
+
exclusiveMinimum: z.number().optional(),
|
|
896
|
+
exclusiveMaximum: z.number().optional(),
|
|
897
|
+
minLength: z.number().optional(),
|
|
898
|
+
properties: z.record(z.string(), ruleConditionSchema).optional(),
|
|
899
|
+
required: z.array(z.string()).optional(),
|
|
900
|
+
allOf: z.array(ruleConditionSchema).optional()
|
|
901
|
+
}).strict()
|
|
902
|
+
);
|
|
903
|
+
schemaBasedConditionSchema = z.object({
|
|
904
|
+
scope: jsonPointerSchema,
|
|
905
|
+
schema: ruleConditionSchema
|
|
906
|
+
}).strict();
|
|
907
|
+
ruleSchema = z.object({
|
|
908
|
+
effect: ruleEffectSchema,
|
|
909
|
+
condition: schemaBasedConditionSchema
|
|
910
|
+
}).strict();
|
|
911
|
+
uiSchemaElementSchema = z.lazy(
|
|
912
|
+
() => z.union([
|
|
913
|
+
controlSchema,
|
|
914
|
+
verticalLayoutSchema,
|
|
915
|
+
horizontalLayoutSchema,
|
|
916
|
+
groupLayoutSchema,
|
|
917
|
+
categorizationSchema,
|
|
918
|
+
categorySchema,
|
|
919
|
+
labelElementSchema
|
|
920
|
+
])
|
|
921
|
+
);
|
|
922
|
+
controlSchema = z.object({
|
|
923
|
+
type: z.literal("Control"),
|
|
924
|
+
scope: jsonPointerSchema,
|
|
925
|
+
label: z.union([z.string(), z.literal(false)]).optional(),
|
|
926
|
+
rule: ruleSchema.optional(),
|
|
927
|
+
options: z.record(z.string(), z.unknown()).optional()
|
|
928
|
+
}).passthrough();
|
|
929
|
+
verticalLayoutSchema = z.lazy(
|
|
930
|
+
() => z.object({
|
|
931
|
+
type: z.literal("VerticalLayout"),
|
|
932
|
+
elements: z.array(uiSchemaElementSchema),
|
|
933
|
+
rule: ruleSchema.optional(),
|
|
934
|
+
options: z.record(z.string(), z.unknown()).optional()
|
|
935
|
+
}).passthrough()
|
|
936
|
+
);
|
|
937
|
+
horizontalLayoutSchema = z.lazy(
|
|
938
|
+
() => z.object({
|
|
939
|
+
type: z.literal("HorizontalLayout"),
|
|
940
|
+
elements: z.array(uiSchemaElementSchema),
|
|
941
|
+
rule: ruleSchema.optional(),
|
|
942
|
+
options: z.record(z.string(), z.unknown()).optional()
|
|
943
|
+
}).passthrough()
|
|
944
|
+
);
|
|
945
|
+
groupLayoutSchema = z.lazy(
|
|
946
|
+
() => z.object({
|
|
947
|
+
type: z.literal("Group"),
|
|
948
|
+
label: z.string(),
|
|
949
|
+
elements: z.array(uiSchemaElementSchema),
|
|
950
|
+
rule: ruleSchema.optional(),
|
|
951
|
+
options: z.record(z.string(), z.unknown()).optional()
|
|
952
|
+
}).passthrough()
|
|
953
|
+
);
|
|
954
|
+
categorySchema = z.lazy(
|
|
955
|
+
() => z.object({
|
|
956
|
+
type: z.literal("Category"),
|
|
957
|
+
label: z.string(),
|
|
958
|
+
elements: z.array(uiSchemaElementSchema),
|
|
959
|
+
rule: ruleSchema.optional(),
|
|
960
|
+
options: z.record(z.string(), z.unknown()).optional()
|
|
961
|
+
}).passthrough()
|
|
962
|
+
);
|
|
963
|
+
categorizationSchema = z.lazy(
|
|
964
|
+
() => z.object({
|
|
965
|
+
type: z.literal("Categorization"),
|
|
966
|
+
elements: z.array(categorySchema),
|
|
967
|
+
label: z.string().optional(),
|
|
968
|
+
rule: ruleSchema.optional(),
|
|
969
|
+
options: z.record(z.string(), z.unknown()).optional()
|
|
970
|
+
}).passthrough()
|
|
971
|
+
);
|
|
972
|
+
labelElementSchema = z.object({
|
|
973
|
+
type: z.literal("Label"),
|
|
974
|
+
text: z.string(),
|
|
975
|
+
rule: ruleSchema.optional(),
|
|
976
|
+
options: z.record(z.string(), z.unknown()).optional()
|
|
977
|
+
}).passthrough();
|
|
978
|
+
uiSchema = z.lazy(
|
|
979
|
+
() => z.union([verticalLayoutSchema, horizontalLayoutSchema, groupLayoutSchema, categorizationSchema])
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
// src/ui-schema/ir-generator.ts
|
|
985
|
+
import { z as z2 } from "zod";
|
|
986
|
+
function parseOrThrow(schema, value, label) {
|
|
987
|
+
try {
|
|
988
|
+
return schema.parse(value);
|
|
989
|
+
} catch (error) {
|
|
990
|
+
if (error instanceof z2.ZodError) {
|
|
991
|
+
throw new Error(
|
|
992
|
+
`Generated ${label} failed validation:
|
|
993
|
+
${error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n")}`
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
throw error;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
function fieldToScope(fieldName) {
|
|
1000
|
+
return `#/properties/${fieldName}`;
|
|
1001
|
+
}
|
|
1002
|
+
function createShowRule(fieldName, value) {
|
|
1003
|
+
return {
|
|
1004
|
+
effect: "SHOW",
|
|
1005
|
+
condition: {
|
|
1006
|
+
scope: fieldToScope(fieldName),
|
|
1007
|
+
schema: { const: value }
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
function flattenConditionSchema(scope, schema) {
|
|
1012
|
+
if (schema.allOf === void 0) {
|
|
1013
|
+
if (scope === "#") {
|
|
1014
|
+
return [schema];
|
|
1015
|
+
}
|
|
1016
|
+
const fieldName = scope.replace("#/properties/", "");
|
|
1017
|
+
return [
|
|
1018
|
+
{
|
|
1019
|
+
properties: {
|
|
1020
|
+
[fieldName]: schema
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
];
|
|
1024
|
+
}
|
|
1025
|
+
return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
|
|
1026
|
+
}
|
|
1027
|
+
function combineRules(parentRule, childRule) {
|
|
1028
|
+
return {
|
|
1029
|
+
effect: "SHOW",
|
|
1030
|
+
condition: {
|
|
1031
|
+
scope: "#",
|
|
1032
|
+
schema: {
|
|
1033
|
+
allOf: [
|
|
1034
|
+
...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
|
|
1035
|
+
...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
|
|
1036
|
+
]
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
function fieldNodeToControl(field, parentRule) {
|
|
1042
|
+
const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
|
|
1043
|
+
const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
|
|
1044
|
+
const control = {
|
|
1045
|
+
type: "Control",
|
|
1046
|
+
scope: fieldToScope(field.name),
|
|
1047
|
+
...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
|
|
1048
|
+
...placeholderAnnotation !== void 0 && {
|
|
1049
|
+
options: { placeholder: placeholderAnnotation.value }
|
|
1050
|
+
},
|
|
1051
|
+
...parentRule !== void 0 && { rule: parentRule }
|
|
1052
|
+
};
|
|
1053
|
+
return control;
|
|
1054
|
+
}
|
|
1055
|
+
function groupNodeToLayout(group, parentRule) {
|
|
1056
|
+
return {
|
|
1057
|
+
type: "Group",
|
|
1058
|
+
label: group.label,
|
|
1059
|
+
elements: irElementsToUiSchema(group.elements, parentRule),
|
|
1060
|
+
...parentRule !== void 0 && { rule: parentRule }
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
function irElementsToUiSchema(elements, parentRule) {
|
|
1064
|
+
const result = [];
|
|
1065
|
+
for (const element of elements) {
|
|
1066
|
+
switch (element.kind) {
|
|
1067
|
+
case "field": {
|
|
1068
|
+
result.push(fieldNodeToControl(element, parentRule));
|
|
1069
|
+
break;
|
|
1070
|
+
}
|
|
1071
|
+
case "group": {
|
|
1072
|
+
result.push(groupNodeToLayout(element, parentRule));
|
|
1073
|
+
break;
|
|
1074
|
+
}
|
|
1075
|
+
case "conditional": {
|
|
1076
|
+
const newRule = createShowRule(element.fieldName, element.value);
|
|
1077
|
+
const combinedRule = parentRule !== void 0 ? combineRules(parentRule, newRule) : newRule;
|
|
1078
|
+
const childElements = irElementsToUiSchema(element.elements, combinedRule);
|
|
1079
|
+
result.push(...childElements);
|
|
1080
|
+
break;
|
|
1081
|
+
}
|
|
1082
|
+
default: {
|
|
1083
|
+
const _exhaustive = element;
|
|
1084
|
+
void _exhaustive;
|
|
1085
|
+
throw new Error("Unhandled IR element kind");
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
return result;
|
|
1090
|
+
}
|
|
1091
|
+
function generateUiSchemaFromIR(ir) {
|
|
1092
|
+
const result = {
|
|
1093
|
+
type: "VerticalLayout",
|
|
1094
|
+
elements: irElementsToUiSchema(ir.elements)
|
|
1095
|
+
};
|
|
1096
|
+
return parseOrThrow(uiSchema, result, "UI Schema");
|
|
1097
|
+
}
|
|
1098
|
+
var init_ir_generator2 = __esm({
|
|
1099
|
+
"src/ui-schema/ir-generator.ts"() {
|
|
1100
|
+
"use strict";
|
|
1101
|
+
init_schema();
|
|
1102
|
+
}
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
// src/ui-schema/generator.ts
|
|
1106
|
+
function generateUiSchema(form) {
|
|
1107
|
+
const ir = canonicalizeChainDSL(form);
|
|
1108
|
+
return generateUiSchemaFromIR(ir);
|
|
1109
|
+
}
|
|
1110
|
+
var init_generator2 = __esm({
|
|
1111
|
+
"src/ui-schema/generator.ts"() {
|
|
1112
|
+
"use strict";
|
|
1113
|
+
init_canonicalize();
|
|
1114
|
+
init_ir_generator2();
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
// src/json-schema/types.ts
|
|
1119
|
+
function setSchemaExtension(schema, key, value) {
|
|
1120
|
+
schema[key] = value;
|
|
1121
|
+
}
|
|
1122
|
+
function getSchemaExtension(schema, key) {
|
|
1123
|
+
return schema[key];
|
|
1124
|
+
}
|
|
1125
|
+
var init_types = __esm({
|
|
1126
|
+
"src/json-schema/types.ts"() {
|
|
1127
|
+
"use strict";
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
1130
|
+
|
|
1131
|
+
// src/extensions/registry.ts
|
|
1132
|
+
function createExtensionRegistry(extensions) {
|
|
1133
|
+
const typeMap = /* @__PURE__ */ new Map();
|
|
1134
|
+
const typeNameMap = /* @__PURE__ */ new Map();
|
|
1135
|
+
const constraintMap = /* @__PURE__ */ new Map();
|
|
1136
|
+
const constraintTagMap = /* @__PURE__ */ new Map();
|
|
1137
|
+
const builtinBroadeningMap = /* @__PURE__ */ new Map();
|
|
1138
|
+
const annotationMap = /* @__PURE__ */ new Map();
|
|
1139
|
+
for (const ext of extensions) {
|
|
1140
|
+
if (ext.types !== void 0) {
|
|
1141
|
+
for (const type of ext.types) {
|
|
1142
|
+
const qualifiedId = `${ext.extensionId}/${type.typeName}`;
|
|
1143
|
+
if (typeMap.has(qualifiedId)) {
|
|
1144
|
+
throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
|
|
1145
|
+
}
|
|
1146
|
+
typeMap.set(qualifiedId, type);
|
|
1147
|
+
for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
|
|
1148
|
+
if (typeNameMap.has(sourceTypeName)) {
|
|
1149
|
+
throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
|
|
1150
|
+
}
|
|
1151
|
+
typeNameMap.set(sourceTypeName, {
|
|
1152
|
+
extensionId: ext.extensionId,
|
|
1153
|
+
registration: type
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
if (type.builtinConstraintBroadenings !== void 0) {
|
|
1157
|
+
for (const broadening of type.builtinConstraintBroadenings) {
|
|
1158
|
+
const key = `${qualifiedId}:${broadening.tagName}`;
|
|
1159
|
+
if (builtinBroadeningMap.has(key)) {
|
|
1160
|
+
throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
|
|
1161
|
+
}
|
|
1162
|
+
builtinBroadeningMap.set(key, {
|
|
1163
|
+
extensionId: ext.extensionId,
|
|
1164
|
+
registration: broadening
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
if (ext.constraints !== void 0) {
|
|
1171
|
+
for (const constraint of ext.constraints) {
|
|
1172
|
+
const qualifiedId = `${ext.extensionId}/${constraint.constraintName}`;
|
|
1173
|
+
if (constraintMap.has(qualifiedId)) {
|
|
1174
|
+
throw new Error(`Duplicate custom constraint ID: "${qualifiedId}"`);
|
|
1175
|
+
}
|
|
1176
|
+
constraintMap.set(qualifiedId, constraint);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
if (ext.constraintTags !== void 0) {
|
|
1180
|
+
for (const tag of ext.constraintTags) {
|
|
1181
|
+
if (constraintTagMap.has(tag.tagName)) {
|
|
1182
|
+
throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
|
|
1183
|
+
}
|
|
1184
|
+
constraintTagMap.set(tag.tagName, {
|
|
1185
|
+
extensionId: ext.extensionId,
|
|
1186
|
+
registration: tag
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
if (ext.annotations !== void 0) {
|
|
1191
|
+
for (const annotation of ext.annotations) {
|
|
1192
|
+
const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
|
|
1193
|
+
if (annotationMap.has(qualifiedId)) {
|
|
1194
|
+
throw new Error(`Duplicate custom annotation ID: "${qualifiedId}"`);
|
|
1195
|
+
}
|
|
1196
|
+
annotationMap.set(qualifiedId, annotation);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
return {
|
|
1201
|
+
extensions,
|
|
1202
|
+
findType: (typeId) => typeMap.get(typeId),
|
|
1203
|
+
findTypeByName: (typeName) => typeNameMap.get(typeName),
|
|
1204
|
+
findConstraint: (constraintId) => constraintMap.get(constraintId),
|
|
1205
|
+
findConstraintTag: (tagName) => constraintTagMap.get(tagName),
|
|
1206
|
+
findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
|
|
1207
|
+
findAnnotation: (annotationId) => annotationMap.get(annotationId)
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
var init_registry = __esm({
|
|
1211
|
+
"src/extensions/registry.ts"() {
|
|
1212
|
+
"use strict";
|
|
1213
|
+
}
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
// src/extensions/index.ts
|
|
1217
|
+
var init_extensions = __esm({
|
|
1218
|
+
"src/extensions/index.ts"() {
|
|
1219
|
+
"use strict";
|
|
1220
|
+
init_registry();
|
|
1221
|
+
}
|
|
1222
|
+
});
|
|
1223
|
+
|
|
1224
|
+
// src/json-schema/schema.ts
|
|
1225
|
+
import { z as z3 } from "zod";
|
|
1226
|
+
var jsonSchemaTypeSchema, jsonSchema7Schema;
|
|
1227
|
+
var init_schema2 = __esm({
|
|
1228
|
+
"src/json-schema/schema.ts"() {
|
|
1229
|
+
"use strict";
|
|
1230
|
+
jsonSchemaTypeSchema = z3.enum([
|
|
1231
|
+
"string",
|
|
1232
|
+
"number",
|
|
1233
|
+
"integer",
|
|
1234
|
+
"boolean",
|
|
1235
|
+
"object",
|
|
1236
|
+
"array",
|
|
1237
|
+
"null"
|
|
1238
|
+
]);
|
|
1239
|
+
jsonSchema7Schema = z3.lazy(
|
|
1240
|
+
() => z3.object({
|
|
1241
|
+
$schema: z3.string().optional(),
|
|
1242
|
+
$id: z3.string().optional(),
|
|
1243
|
+
$ref: z3.string().optional(),
|
|
1244
|
+
// Metadata
|
|
1245
|
+
title: z3.string().optional(),
|
|
1246
|
+
description: z3.string().optional(),
|
|
1247
|
+
deprecated: z3.boolean().optional(),
|
|
1248
|
+
// Type
|
|
1249
|
+
type: z3.union([jsonSchemaTypeSchema, z3.array(jsonSchemaTypeSchema)]).optional(),
|
|
1250
|
+
// String validation
|
|
1251
|
+
minLength: z3.number().optional(),
|
|
1252
|
+
maxLength: z3.number().optional(),
|
|
1253
|
+
pattern: z3.string().optional(),
|
|
1254
|
+
// Number validation
|
|
1255
|
+
minimum: z3.number().optional(),
|
|
1256
|
+
maximum: z3.number().optional(),
|
|
1257
|
+
exclusiveMinimum: z3.number().optional(),
|
|
1258
|
+
exclusiveMaximum: z3.number().optional(),
|
|
1259
|
+
// Enum
|
|
1260
|
+
enum: z3.array(z3.union([z3.string(), z3.number(), z3.boolean(), z3.null()])).readonly().optional(),
|
|
1261
|
+
const: z3.union([z3.string(), z3.number(), z3.boolean(), z3.null()]).optional(),
|
|
1262
|
+
// Object
|
|
1263
|
+
properties: z3.record(z3.string(), jsonSchema7Schema).optional(),
|
|
1264
|
+
required: z3.array(z3.string()).optional(),
|
|
1265
|
+
additionalProperties: z3.union([z3.boolean(), jsonSchema7Schema]).optional(),
|
|
1266
|
+
// Array
|
|
1267
|
+
items: z3.union([jsonSchema7Schema, z3.array(jsonSchema7Schema)]).optional(),
|
|
1268
|
+
minItems: z3.number().optional(),
|
|
1269
|
+
maxItems: z3.number().optional(),
|
|
1270
|
+
// Composition
|
|
1271
|
+
allOf: z3.array(jsonSchema7Schema).optional(),
|
|
1272
|
+
anyOf: z3.array(jsonSchema7Schema).optional(),
|
|
1273
|
+
oneOf: z3.array(jsonSchema7Schema).optional(),
|
|
1274
|
+
not: jsonSchema7Schema.optional(),
|
|
1275
|
+
// Conditional
|
|
1276
|
+
if: jsonSchema7Schema.optional(),
|
|
1277
|
+
then: jsonSchema7Schema.optional(),
|
|
1278
|
+
else: jsonSchema7Schema.optional(),
|
|
1279
|
+
// Format
|
|
1280
|
+
format: z3.string().optional(),
|
|
1281
|
+
// Default
|
|
1282
|
+
default: z3.unknown().optional(),
|
|
1283
|
+
// FormSpec extensions
|
|
1284
|
+
"x-formspec-source": z3.string().optional(),
|
|
1285
|
+
"x-formspec-params": z3.array(z3.string()).readonly().optional(),
|
|
1286
|
+
"x-formspec-schemaSource": z3.string().optional()
|
|
1287
|
+
}).passthrough()
|
|
1288
|
+
);
|
|
1289
|
+
}
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
// src/analyzer/tsdoc-parser.ts
|
|
1293
|
+
import * as ts from "typescript";
|
|
1294
|
+
import {
|
|
1295
|
+
checkSyntheticTagApplication,
|
|
1296
|
+
extractPathTarget as extractSharedPathTarget,
|
|
1297
|
+
getTagDefinition,
|
|
1298
|
+
hasTypeSemanticCapability,
|
|
1299
|
+
parseConstraintTagValue,
|
|
1300
|
+
parseDefaultValueTagValue,
|
|
1301
|
+
resolveDeclarationPlacement,
|
|
1302
|
+
resolvePathTargetType,
|
|
1303
|
+
sliceCommentSpan,
|
|
1304
|
+
parseCommentBlock,
|
|
1305
|
+
parseTagSyntax
|
|
1306
|
+
} from "@formspec/analysis";
|
|
1307
|
+
import {
|
|
1308
|
+
TSDocParser,
|
|
1309
|
+
TSDocConfiguration,
|
|
1310
|
+
TSDocTagDefinition,
|
|
1311
|
+
TSDocTagSyntaxKind,
|
|
1312
|
+
DocPlainText,
|
|
1313
|
+
DocSoftBreak,
|
|
1314
|
+
TextRange
|
|
1315
|
+
} from "@microsoft/tsdoc";
|
|
1316
|
+
import {
|
|
1317
|
+
BUILTIN_CONSTRAINT_DEFINITIONS,
|
|
1318
|
+
normalizeConstraintTagName,
|
|
1319
|
+
isBuiltinConstraintName
|
|
1320
|
+
} from "@formspec/core";
|
|
1321
|
+
function createFormSpecTSDocConfig(extensionTagNames = []) {
|
|
1322
|
+
const config = new TSDocConfiguration();
|
|
1323
|
+
for (const tagName of Object.keys(BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
1324
|
+
config.addTagDefinition(
|
|
1325
|
+
new TSDocTagDefinition({
|
|
1326
|
+
tagName: "@" + tagName,
|
|
1327
|
+
syntaxKind: TSDocTagSyntaxKind.BlockTag,
|
|
1328
|
+
allowMultiple: true
|
|
1329
|
+
})
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
for (const tagName of ["displayName", "description", "format", "placeholder"]) {
|
|
1333
|
+
config.addTagDefinition(
|
|
1334
|
+
new TSDocTagDefinition({
|
|
1335
|
+
tagName: "@" + tagName,
|
|
1336
|
+
syntaxKind: TSDocTagSyntaxKind.BlockTag,
|
|
1337
|
+
allowMultiple: true
|
|
1338
|
+
})
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
for (const tagName of extensionTagNames) {
|
|
1342
|
+
config.addTagDefinition(
|
|
1343
|
+
new TSDocTagDefinition({
|
|
1344
|
+
tagName: "@" + tagName,
|
|
1345
|
+
syntaxKind: TSDocTagSyntaxKind.BlockTag,
|
|
1346
|
+
allowMultiple: true
|
|
1347
|
+
})
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
return config;
|
|
1351
|
+
}
|
|
1352
|
+
function sharedCommentSyntaxOptions(options, offset) {
|
|
1353
|
+
const extensions = options?.extensionRegistry?.extensions;
|
|
1354
|
+
return {
|
|
1355
|
+
...offset !== void 0 ? { offset } : {},
|
|
1356
|
+
...extensions !== void 0 ? { extensions } : {}
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
function sharedTagValueOptions(options) {
|
|
1360
|
+
return {
|
|
1361
|
+
...options?.extensionRegistry !== void 0 ? { registry: options.extensionRegistry } : {},
|
|
1362
|
+
...options?.fieldType !== void 0 ? { fieldType: options.fieldType } : {}
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
function buildSupportingDeclarations(sourceFile) {
|
|
1366
|
+
return sourceFile.statements.filter(
|
|
1367
|
+
(statement) => !ts.isImportDeclaration(statement) && !ts.isImportEqualsDeclaration(statement) && !(ts.isExportDeclaration(statement) && statement.moduleSpecifier !== void 0)
|
|
1368
|
+
).map((statement) => statement.getText(sourceFile));
|
|
1369
|
+
}
|
|
1370
|
+
function renderSyntheticArgumentExpression(valueKind, argumentText) {
|
|
1371
|
+
const trimmed = argumentText.trim();
|
|
1372
|
+
if (trimmed === "") {
|
|
1373
|
+
return null;
|
|
1374
|
+
}
|
|
1375
|
+
switch (valueKind) {
|
|
1376
|
+
case "number":
|
|
1377
|
+
case "integer":
|
|
1378
|
+
case "signedInteger":
|
|
1379
|
+
return Number.isFinite(Number(trimmed)) ? trimmed : JSON.stringify(trimmed);
|
|
1380
|
+
case "string":
|
|
1381
|
+
return JSON.stringify(argumentText);
|
|
1382
|
+
case "json":
|
|
1383
|
+
try {
|
|
1384
|
+
JSON.parse(trimmed);
|
|
1385
|
+
return `(${trimmed})`;
|
|
1386
|
+
} catch {
|
|
1387
|
+
return JSON.stringify(trimmed);
|
|
1388
|
+
}
|
|
1389
|
+
case "boolean":
|
|
1390
|
+
return trimmed === "true" || trimmed === "false" ? trimmed : JSON.stringify(trimmed);
|
|
1391
|
+
case "condition":
|
|
1392
|
+
return "undefined as unknown as FormSpecCondition";
|
|
1393
|
+
case null:
|
|
1394
|
+
return null;
|
|
1395
|
+
default: {
|
|
1396
|
+
return String(valueKind);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
function getArrayElementType(type, checker) {
|
|
1401
|
+
if (!checker.isArrayType(type)) {
|
|
1402
|
+
return null;
|
|
1403
|
+
}
|
|
1404
|
+
return checker.getTypeArguments(type)[0] ?? null;
|
|
1405
|
+
}
|
|
1406
|
+
function supportsConstraintCapability(type, checker, capability) {
|
|
1407
|
+
if (capability === void 0) {
|
|
1408
|
+
return true;
|
|
1409
|
+
}
|
|
1410
|
+
if (hasTypeSemanticCapability(type, checker, capability)) {
|
|
1411
|
+
return true;
|
|
1412
|
+
}
|
|
1413
|
+
if (capability === "string-like") {
|
|
1414
|
+
const itemType = getArrayElementType(type, checker);
|
|
1415
|
+
return itemType !== null && hasTypeSemanticCapability(itemType, checker, capability);
|
|
1416
|
+
}
|
|
1417
|
+
return false;
|
|
1418
|
+
}
|
|
1419
|
+
function makeDiagnostic(code, message, provenance) {
|
|
1420
|
+
return {
|
|
1421
|
+
code,
|
|
1422
|
+
message,
|
|
1423
|
+
severity: "error",
|
|
1424
|
+
primaryLocation: provenance,
|
|
1425
|
+
relatedLocations: []
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
function placementLabel(placement) {
|
|
1429
|
+
switch (placement) {
|
|
1430
|
+
case "class":
|
|
1431
|
+
return "class declarations";
|
|
1432
|
+
case "class-field":
|
|
1433
|
+
return "class fields";
|
|
1434
|
+
case "class-method":
|
|
1435
|
+
return "class methods";
|
|
1436
|
+
case "interface":
|
|
1437
|
+
return "interface declarations";
|
|
1438
|
+
case "interface-field":
|
|
1439
|
+
return "interface fields";
|
|
1440
|
+
case "type-alias":
|
|
1441
|
+
return "type aliases";
|
|
1442
|
+
case "type-alias-field":
|
|
1443
|
+
return "type-alias properties";
|
|
1444
|
+
case "variable":
|
|
1445
|
+
return "variables";
|
|
1446
|
+
case "function":
|
|
1447
|
+
return "functions";
|
|
1448
|
+
case "function-parameter":
|
|
1449
|
+
return "function parameters";
|
|
1450
|
+
case "method-parameter":
|
|
1451
|
+
return "method parameters";
|
|
1452
|
+
default: {
|
|
1453
|
+
const exhaustive = placement;
|
|
1454
|
+
return String(exhaustive);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
function capabilityLabel(capability) {
|
|
1459
|
+
switch (capability) {
|
|
1460
|
+
case "numeric-comparable":
|
|
1461
|
+
return "number";
|
|
1462
|
+
case "string-like":
|
|
1463
|
+
return "string";
|
|
1464
|
+
case "array-like":
|
|
1465
|
+
return "array";
|
|
1466
|
+
case "enum-member-addressable":
|
|
1467
|
+
return "enum";
|
|
1468
|
+
case "json-like":
|
|
1469
|
+
return "JSON-compatible";
|
|
1470
|
+
case "object-like":
|
|
1471
|
+
return "object";
|
|
1472
|
+
case "condition-like":
|
|
1473
|
+
return "conditional";
|
|
1474
|
+
case void 0:
|
|
1475
|
+
return "compatible";
|
|
1476
|
+
default:
|
|
1477
|
+
return capability;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
function getBroadenedCustomTypeId(fieldType) {
|
|
1481
|
+
if (fieldType?.kind === "custom") {
|
|
1482
|
+
return fieldType.typeId;
|
|
1483
|
+
}
|
|
1484
|
+
if (fieldType?.kind !== "union") {
|
|
1485
|
+
return void 0;
|
|
1486
|
+
}
|
|
1487
|
+
const customMembers = fieldType.members.filter(
|
|
1488
|
+
(member) => member.kind === "custom"
|
|
1489
|
+
);
|
|
1490
|
+
if (customMembers.length !== 1) {
|
|
1491
|
+
return void 0;
|
|
1492
|
+
}
|
|
1493
|
+
const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
|
|
1494
|
+
const allOtherMembersAreNull = nonCustomMembers.every(
|
|
1495
|
+
(member) => member.kind === "primitive" && member.primitiveKind === "null"
|
|
1496
|
+
);
|
|
1497
|
+
const customMember = customMembers[0];
|
|
1498
|
+
return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
|
|
1499
|
+
}
|
|
1500
|
+
function hasBuiltinConstraintBroadening(tagName, options) {
|
|
1501
|
+
const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
|
|
1502
|
+
return broadenedTypeId !== void 0 && options?.extensionRegistry?.findBuiltinConstraintBroadening(broadenedTypeId, tagName) !== void 0;
|
|
1503
|
+
}
|
|
1504
|
+
function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, parsedTag, provenance, supportingDeclarations, options) {
|
|
1505
|
+
if (!isBuiltinConstraintName(tagName)) {
|
|
1506
|
+
return [];
|
|
1507
|
+
}
|
|
1508
|
+
const checker = options?.checker;
|
|
1509
|
+
const subjectType = options?.subjectType;
|
|
1510
|
+
if (checker === void 0 || subjectType === void 0) {
|
|
1511
|
+
return [];
|
|
1512
|
+
}
|
|
1513
|
+
const placement = resolveDeclarationPlacement(node);
|
|
1514
|
+
if (placement === null) {
|
|
1515
|
+
return [];
|
|
1516
|
+
}
|
|
1517
|
+
const definition = getTagDefinition(tagName, options?.extensionRegistry?.extensions);
|
|
1518
|
+
if (definition === null) {
|
|
1519
|
+
return [];
|
|
1520
|
+
}
|
|
1521
|
+
if (!definition.placements.includes(placement)) {
|
|
1522
|
+
return [
|
|
1523
|
+
makeDiagnostic(
|
|
1524
|
+
"INVALID_TAG_PLACEMENT",
|
|
1525
|
+
`Tag "@${tagName}" is not allowed on ${placementLabel(placement)}.`,
|
|
1526
|
+
provenance
|
|
1527
|
+
)
|
|
1528
|
+
];
|
|
1529
|
+
}
|
|
1530
|
+
const target = parsedTag?.target ?? null;
|
|
1531
|
+
const hasBroadening = target === null && hasBuiltinConstraintBroadening(tagName, options);
|
|
1532
|
+
if (target !== null) {
|
|
1533
|
+
if (target.kind !== "path") {
|
|
1534
|
+
return [
|
|
1535
|
+
makeDiagnostic(
|
|
1536
|
+
"UNSUPPORTED_TARGETING_SYNTAX",
|
|
1537
|
+
`Tag "@${tagName}" does not support ${target.kind} targeting syntax.`,
|
|
1538
|
+
provenance
|
|
1539
|
+
)
|
|
1540
|
+
];
|
|
1541
|
+
}
|
|
1542
|
+
if (!target.valid || target.path === null) {
|
|
1543
|
+
return [
|
|
1544
|
+
makeDiagnostic(
|
|
1545
|
+
"UNSUPPORTED_TARGETING_SYNTAX",
|
|
1546
|
+
`Tag "@${tagName}" has invalid path targeting syntax.`,
|
|
1547
|
+
provenance
|
|
1548
|
+
)
|
|
1549
|
+
];
|
|
1550
|
+
}
|
|
1551
|
+
const resolution = resolvePathTargetType(subjectType, checker, target.path.segments);
|
|
1552
|
+
if (resolution.kind === "missing-property") {
|
|
1553
|
+
return [
|
|
1554
|
+
makeDiagnostic(
|
|
1555
|
+
"UNKNOWN_PATH_TARGET",
|
|
1556
|
+
`Target "${target.rawText}": path-targeted constraint "${tagName}" references unknown path segment "${resolution.segment}"`,
|
|
1557
|
+
provenance
|
|
1558
|
+
)
|
|
1559
|
+
];
|
|
1560
|
+
}
|
|
1561
|
+
if (resolution.kind === "unresolvable") {
|
|
1562
|
+
const actualType = checker.typeToString(resolution.type, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
1563
|
+
return [
|
|
1564
|
+
makeDiagnostic(
|
|
1565
|
+
"TYPE_MISMATCH",
|
|
1566
|
+
`Target "${target.rawText}": path-targeted constraint "${tagName}" is invalid because type "${actualType}" cannot be traversed`,
|
|
1567
|
+
provenance
|
|
1568
|
+
)
|
|
1569
|
+
];
|
|
1570
|
+
}
|
|
1571
|
+
const requiredCapability = definition.capabilities[0];
|
|
1572
|
+
if (requiredCapability !== void 0 && !supportsConstraintCapability(resolution.type, checker, requiredCapability)) {
|
|
1573
|
+
const actualType = checker.typeToString(resolution.type, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
1574
|
+
return [
|
|
1575
|
+
makeDiagnostic(
|
|
1576
|
+
"TYPE_MISMATCH",
|
|
1577
|
+
`Target "${target.rawText}": constraint "${tagName}" is only valid on ${capabilityLabel(requiredCapability)} targets, but field type is "${actualType}"`,
|
|
1578
|
+
provenance
|
|
1579
|
+
)
|
|
1580
|
+
];
|
|
1581
|
+
}
|
|
1582
|
+
} else if (!hasBroadening) {
|
|
1583
|
+
const requiredCapability = definition.capabilities[0];
|
|
1584
|
+
if (requiredCapability !== void 0 && !supportsConstraintCapability(subjectType, checker, requiredCapability)) {
|
|
1585
|
+
const actualType = checker.typeToString(subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
1586
|
+
return [
|
|
1587
|
+
makeDiagnostic(
|
|
1588
|
+
"TYPE_MISMATCH",
|
|
1589
|
+
`Target "${node.getText(sourceFile)}": constraint "${tagName}" is only valid on ${capabilityLabel(requiredCapability)} targets, but field type is "${actualType}"`,
|
|
1590
|
+
provenance
|
|
1591
|
+
)
|
|
1592
|
+
];
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
const argumentExpression = renderSyntheticArgumentExpression(
|
|
1596
|
+
definition.valueKind,
|
|
1597
|
+
parsedTag?.argumentText ?? ""
|
|
1598
|
+
);
|
|
1599
|
+
if (definition.requiresArgument && argumentExpression === null) {
|
|
1600
|
+
return [];
|
|
1601
|
+
}
|
|
1602
|
+
if (hasBroadening) {
|
|
1603
|
+
return [];
|
|
1604
|
+
}
|
|
1605
|
+
const subjectTypeText = checker.typeToString(subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
1606
|
+
const hostType = options?.hostType ?? subjectType;
|
|
1607
|
+
const hostTypeText = checker.typeToString(hostType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
1608
|
+
const result = checkSyntheticTagApplication({
|
|
1609
|
+
tagName,
|
|
1610
|
+
placement,
|
|
1611
|
+
hostType: hostTypeText,
|
|
1612
|
+
subjectType: subjectTypeText,
|
|
1613
|
+
...target?.kind === "path" ? { target: { kind: "path", text: target.rawText } } : {},
|
|
1614
|
+
...argumentExpression !== null ? { argumentExpression } : {},
|
|
1615
|
+
supportingDeclarations,
|
|
1616
|
+
...options?.extensionRegistry !== void 0 ? {
|
|
1617
|
+
extensions: options.extensionRegistry.extensions.map((extension) => ({
|
|
1618
|
+
extensionId: extension.extensionId,
|
|
1619
|
+
...extension.constraintTags !== void 0 ? {
|
|
1620
|
+
constraintTags: extension.constraintTags.map((tag) => ({ tagName: tag.tagName }))
|
|
1621
|
+
} : {}
|
|
1622
|
+
}))
|
|
1623
|
+
} : {}
|
|
1624
|
+
});
|
|
1625
|
+
if (result.diagnostics.length === 0) {
|
|
1626
|
+
return [];
|
|
1627
|
+
}
|
|
1628
|
+
const expectedLabel = definition.valueKind === null ? "compatible argument" : capabilityLabel(definition.valueKind);
|
|
1629
|
+
return [
|
|
1630
|
+
makeDiagnostic(
|
|
1631
|
+
"TYPE_MISMATCH",
|
|
1632
|
+
`Tag "@${tagName}" received an invalid argument for ${expectedLabel}.`,
|
|
1633
|
+
provenance
|
|
1634
|
+
)
|
|
1635
|
+
];
|
|
1636
|
+
}
|
|
1637
|
+
function getParser(options) {
|
|
1638
|
+
const extensionTagNames = [
|
|
1639
|
+
...options?.extensionRegistry?.extensions.flatMap(
|
|
1640
|
+
(extension) => (extension.constraintTags ?? []).map((tag) => tag.tagName)
|
|
1641
|
+
) ?? []
|
|
1642
|
+
].sort();
|
|
1643
|
+
const cacheKey = extensionTagNames.join("|");
|
|
1644
|
+
const existing = parserCache.get(cacheKey);
|
|
1645
|
+
if (existing) {
|
|
1646
|
+
return existing;
|
|
1647
|
+
}
|
|
1648
|
+
const parser = new TSDocParser(createFormSpecTSDocConfig(extensionTagNames));
|
|
1649
|
+
parserCache.set(cacheKey, parser);
|
|
1650
|
+
return parser;
|
|
1651
|
+
}
|
|
1652
|
+
function getExtensionRegistryCacheKey(registry) {
|
|
1653
|
+
if (registry === void 0) {
|
|
1654
|
+
return "";
|
|
1655
|
+
}
|
|
1656
|
+
return registry.extensions.map(
|
|
1657
|
+
(extension) => JSON.stringify({
|
|
1658
|
+
extensionId: extension.extensionId,
|
|
1659
|
+
typeNames: extension.types?.map((type) => type.typeName) ?? [],
|
|
1660
|
+
constraintTags: extension.constraintTags?.map((tag) => tag.tagName) ?? []
|
|
1661
|
+
})
|
|
1662
|
+
).join("|");
|
|
1663
|
+
}
|
|
1664
|
+
function getParseCacheKey(node, file, options) {
|
|
1665
|
+
const sourceFile = node.getSourceFile();
|
|
1666
|
+
const checker = options?.checker;
|
|
1667
|
+
return JSON.stringify({
|
|
1668
|
+
file,
|
|
1669
|
+
sourceFile: sourceFile.fileName,
|
|
1670
|
+
sourceText: sourceFile.text,
|
|
1671
|
+
start: node.getFullStart(),
|
|
1672
|
+
end: node.getEnd(),
|
|
1673
|
+
fieldType: options?.fieldType ?? null,
|
|
1674
|
+
subjectType: checker !== void 0 && options?.subjectType !== void 0 ? checker.typeToString(options.subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS) : null,
|
|
1675
|
+
hostType: checker !== void 0 && options?.hostType !== void 0 ? checker.typeToString(options.hostType, node, SYNTHETIC_TYPE_FORMAT_FLAGS) : null,
|
|
1676
|
+
extensions: getExtensionRegistryCacheKey(options?.extensionRegistry)
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
function parseTSDocTags(node, file = "", options) {
|
|
1680
|
+
const cacheKey = getParseCacheKey(node, file, options);
|
|
1681
|
+
const cached = parseResultCache.get(cacheKey);
|
|
1682
|
+
if (cached !== void 0) {
|
|
1683
|
+
return cached;
|
|
1684
|
+
}
|
|
1685
|
+
const constraints = [];
|
|
1686
|
+
const annotations = [];
|
|
1687
|
+
const diagnostics = [];
|
|
1688
|
+
let displayName;
|
|
1689
|
+
let description;
|
|
1690
|
+
let placeholder;
|
|
1691
|
+
let displayNameProvenance;
|
|
1692
|
+
let descriptionProvenance;
|
|
1693
|
+
let placeholderProvenance;
|
|
1694
|
+
const rawTextTags = [];
|
|
1695
|
+
const sourceFile = node.getSourceFile();
|
|
1696
|
+
const sourceText = sourceFile.getFullText();
|
|
1697
|
+
const supportingDeclarations = buildSupportingDeclarations(sourceFile);
|
|
1698
|
+
const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
1699
|
+
const rawTextFallbacks = collectRawTextFallbacks(node, file);
|
|
1700
|
+
if (commentRanges) {
|
|
1701
|
+
for (const range of commentRanges) {
|
|
1702
|
+
if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
|
|
1703
|
+
continue;
|
|
1704
|
+
}
|
|
1705
|
+
const commentText = sourceText.substring(range.pos, range.end);
|
|
1706
|
+
if (!commentText.startsWith("/**")) {
|
|
1707
|
+
continue;
|
|
1708
|
+
}
|
|
1709
|
+
const parser = getParser(options);
|
|
1710
|
+
const parserContext = parser.parseRange(
|
|
1711
|
+
TextRange.fromStringRange(sourceText, range.pos, range.end)
|
|
1712
|
+
);
|
|
1713
|
+
const docComment = parserContext.docComment;
|
|
1714
|
+
const parsedComment = parseCommentBlock(
|
|
1715
|
+
commentText,
|
|
1716
|
+
sharedCommentSyntaxOptions(options, range.pos)
|
|
1717
|
+
);
|
|
1718
|
+
let parsedTagCursor = 0;
|
|
1719
|
+
const nextParsedTag = (normalizedTagName) => {
|
|
1720
|
+
while (parsedTagCursor < parsedComment.tags.length) {
|
|
1721
|
+
const candidate = parsedComment.tags[parsedTagCursor];
|
|
1722
|
+
parsedTagCursor += 1;
|
|
1723
|
+
if (candidate?.normalizedTagName === normalizedTagName) {
|
|
1724
|
+
return candidate;
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
return null;
|
|
1728
|
+
};
|
|
1729
|
+
for (const parsedTag of parsedComment.tags) {
|
|
1730
|
+
if (TAGS_REQUIRING_RAW_TEXT.has(parsedTag.normalizedTagName)) {
|
|
1731
|
+
rawTextTags.push({ tag: parsedTag, commentText, commentOffset: range.pos });
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
for (const block of docComment.customBlocks) {
|
|
1735
|
+
const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
|
|
1736
|
+
const parsedTag = nextParsedTag(tagName);
|
|
1737
|
+
if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
|
|
1738
|
+
const text2 = getBestBlockPayloadText(parsedTag, commentText, range.pos, block);
|
|
1739
|
+
if (text2 === "") continue;
|
|
1740
|
+
const provenance2 = parsedTag !== null ? provenanceForParsedTag(parsedTag, sourceFile, file) : provenanceForComment(range, sourceFile, file, tagName);
|
|
1741
|
+
switch (tagName) {
|
|
1742
|
+
case "displayName":
|
|
1743
|
+
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
1744
|
+
displayName = text2;
|
|
1745
|
+
displayNameProvenance = provenance2;
|
|
1746
|
+
}
|
|
1747
|
+
break;
|
|
1748
|
+
case "format":
|
|
1749
|
+
annotations.push({
|
|
1750
|
+
kind: "annotation",
|
|
1751
|
+
annotationKind: "format",
|
|
1752
|
+
value: text2,
|
|
1753
|
+
provenance: provenance2
|
|
1754
|
+
});
|
|
1755
|
+
break;
|
|
1756
|
+
case "description":
|
|
1757
|
+
description = text2;
|
|
1758
|
+
descriptionProvenance = provenance2;
|
|
1759
|
+
break;
|
|
1760
|
+
case "placeholder":
|
|
1761
|
+
if (placeholder === void 0) {
|
|
1762
|
+
placeholder = text2;
|
|
1763
|
+
placeholderProvenance = provenance2;
|
|
1764
|
+
}
|
|
1765
|
+
break;
|
|
1766
|
+
}
|
|
1767
|
+
continue;
|
|
1768
|
+
}
|
|
1769
|
+
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1770
|
+
const text = getBestBlockPayloadText(parsedTag, commentText, range.pos, block);
|
|
1771
|
+
const expectedType = isBuiltinConstraintName(tagName) ? BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
|
|
1772
|
+
if (text === "" && expectedType !== "boolean") continue;
|
|
1773
|
+
const provenance = parsedTag !== null ? provenanceForParsedTag(parsedTag, sourceFile, file) : provenanceForComment(range, sourceFile, file, tagName);
|
|
1774
|
+
const compilerDiagnostics = buildCompilerBackedConstraintDiagnostics(
|
|
1775
|
+
node,
|
|
1776
|
+
sourceFile,
|
|
1777
|
+
tagName,
|
|
1778
|
+
parsedTag,
|
|
1779
|
+
provenance,
|
|
1780
|
+
supportingDeclarations,
|
|
1781
|
+
options
|
|
1782
|
+
);
|
|
1783
|
+
if (compilerDiagnostics.length > 0) {
|
|
1784
|
+
diagnostics.push(...compilerDiagnostics);
|
|
1785
|
+
continue;
|
|
1786
|
+
}
|
|
1787
|
+
const constraintNode = parseConstraintTagValue(
|
|
1788
|
+
tagName,
|
|
1789
|
+
text,
|
|
1790
|
+
provenance,
|
|
1791
|
+
sharedTagValueOptions(options)
|
|
1792
|
+
);
|
|
1793
|
+
if (constraintNode) {
|
|
1794
|
+
constraints.push(constraintNode);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
if (docComment.deprecatedBlock !== void 0) {
|
|
1798
|
+
const message = extractBlockText(docComment.deprecatedBlock).trim();
|
|
1799
|
+
annotations.push({
|
|
1800
|
+
kind: "annotation",
|
|
1801
|
+
annotationKind: "deprecated",
|
|
1802
|
+
...message !== "" && { message },
|
|
1803
|
+
provenance: provenanceForComment(range, sourceFile, file, "deprecated")
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
if (description === void 0 && docComment.remarksBlock !== void 0) {
|
|
1807
|
+
const remarks = extractBlockText(docComment.remarksBlock).trim();
|
|
1808
|
+
if (remarks !== "") {
|
|
1809
|
+
description = remarks;
|
|
1810
|
+
descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
if (description === void 0) {
|
|
1814
|
+
const summary = extractPlainText(docComment.summarySection).trim();
|
|
1815
|
+
if (summary !== "") {
|
|
1816
|
+
description = summary;
|
|
1817
|
+
descriptionProvenance = provenanceForComment(range, sourceFile, file, "summary");
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
if (displayName !== void 0 && displayNameProvenance !== void 0) {
|
|
1823
|
+
annotations.push({
|
|
1824
|
+
kind: "annotation",
|
|
1825
|
+
annotationKind: "displayName",
|
|
1826
|
+
value: displayName,
|
|
1827
|
+
provenance: displayNameProvenance
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
if (description !== void 0 && descriptionProvenance !== void 0) {
|
|
1831
|
+
annotations.push({
|
|
1832
|
+
kind: "annotation",
|
|
1833
|
+
annotationKind: "description",
|
|
1834
|
+
value: description,
|
|
1835
|
+
provenance: descriptionProvenance
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1838
|
+
if (placeholder !== void 0 && placeholderProvenance !== void 0) {
|
|
1839
|
+
annotations.push({
|
|
1840
|
+
kind: "annotation",
|
|
1841
|
+
annotationKind: "placeholder",
|
|
1842
|
+
value: placeholder,
|
|
1843
|
+
provenance: placeholderProvenance
|
|
1844
|
+
});
|
|
1845
|
+
}
|
|
1846
|
+
if (rawTextTags.length > 0) {
|
|
1847
|
+
for (const rawTextTag of rawTextTags) {
|
|
1848
|
+
const fallbackQueue = rawTextFallbacks.get(rawTextTag.tag.normalizedTagName);
|
|
1849
|
+
const fallback = fallbackQueue?.shift();
|
|
1850
|
+
const text = choosePreferredPayloadText(
|
|
1851
|
+
getSharedPayloadText(rawTextTag.tag, rawTextTag.commentText, rawTextTag.commentOffset),
|
|
1852
|
+
fallback?.text ?? ""
|
|
1853
|
+
);
|
|
1854
|
+
if (text === "") continue;
|
|
1855
|
+
const provenance = provenanceForParsedTag(rawTextTag.tag, sourceFile, file);
|
|
1856
|
+
if (rawTextTag.tag.normalizedTagName === "defaultValue") {
|
|
1857
|
+
const defaultValueNode = parseDefaultValueTagValue(text, provenance);
|
|
1858
|
+
annotations.push(defaultValueNode);
|
|
1859
|
+
continue;
|
|
1860
|
+
}
|
|
1861
|
+
const compilerDiagnostics = buildCompilerBackedConstraintDiagnostics(
|
|
1862
|
+
node,
|
|
1863
|
+
sourceFile,
|
|
1864
|
+
rawTextTag.tag.normalizedTagName,
|
|
1865
|
+
rawTextTag.tag,
|
|
1866
|
+
provenance,
|
|
1867
|
+
supportingDeclarations,
|
|
1868
|
+
options
|
|
1869
|
+
);
|
|
1870
|
+
if (compilerDiagnostics.length > 0) {
|
|
1871
|
+
diagnostics.push(...compilerDiagnostics);
|
|
1872
|
+
continue;
|
|
1873
|
+
}
|
|
1874
|
+
const constraintNode = parseConstraintTagValue(
|
|
1875
|
+
rawTextTag.tag.normalizedTagName,
|
|
1876
|
+
text,
|
|
1877
|
+
provenance,
|
|
1878
|
+
sharedTagValueOptions(options)
|
|
1879
|
+
);
|
|
1880
|
+
if (constraintNode) {
|
|
1881
|
+
constraints.push(constraintNode);
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
for (const [tagName, fallbacks] of rawTextFallbacks) {
|
|
1886
|
+
for (const fallback of fallbacks) {
|
|
1887
|
+
const text = fallback.text.trim();
|
|
1888
|
+
if (text === "") continue;
|
|
1889
|
+
const provenance = fallback.provenance;
|
|
1890
|
+
if (tagName === "defaultValue") {
|
|
1891
|
+
const defaultValueNode = parseDefaultValueTagValue(text, provenance);
|
|
1892
|
+
annotations.push(defaultValueNode);
|
|
1893
|
+
continue;
|
|
1894
|
+
}
|
|
1895
|
+
const compilerDiagnostics = buildCompilerBackedConstraintDiagnostics(
|
|
1896
|
+
node,
|
|
1897
|
+
sourceFile,
|
|
1898
|
+
tagName,
|
|
1899
|
+
null,
|
|
1900
|
+
provenance,
|
|
1901
|
+
supportingDeclarations,
|
|
1902
|
+
options
|
|
1903
|
+
);
|
|
1904
|
+
if (compilerDiagnostics.length > 0) {
|
|
1905
|
+
diagnostics.push(...compilerDiagnostics);
|
|
1906
|
+
continue;
|
|
1907
|
+
}
|
|
1908
|
+
const constraintNode = parseConstraintTagValue(
|
|
1909
|
+
tagName,
|
|
1910
|
+
text,
|
|
1911
|
+
provenance,
|
|
1912
|
+
sharedTagValueOptions(options)
|
|
1913
|
+
);
|
|
1914
|
+
if (constraintNode) {
|
|
1915
|
+
constraints.push(constraintNode);
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
const result = { constraints, annotations, diagnostics };
|
|
1920
|
+
parseResultCache.set(cacheKey, result);
|
|
1921
|
+
return result;
|
|
1922
|
+
}
|
|
1923
|
+
function extractDisplayNameMetadata(node) {
|
|
1924
|
+
let displayName;
|
|
1925
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1926
|
+
const sourceFile = node.getSourceFile();
|
|
1927
|
+
const sourceText = sourceFile.getFullText();
|
|
1928
|
+
const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
1929
|
+
if (commentRanges) {
|
|
1930
|
+
for (const range of commentRanges) {
|
|
1931
|
+
if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) continue;
|
|
1932
|
+
const commentText = sourceText.substring(range.pos, range.end);
|
|
1933
|
+
if (!commentText.startsWith("/**")) continue;
|
|
1934
|
+
const parsed = parseCommentBlock(commentText);
|
|
1935
|
+
for (const tag of parsed.tags) {
|
|
1936
|
+
if (tag.normalizedTagName !== "displayName") {
|
|
1937
|
+
continue;
|
|
1938
|
+
}
|
|
1939
|
+
if (tag.target !== null && tag.argumentText !== "") {
|
|
1940
|
+
memberDisplayNames.set(tag.target.rawText, tag.argumentText);
|
|
1941
|
+
continue;
|
|
1942
|
+
}
|
|
1943
|
+
if (tag.argumentText !== "") {
|
|
1944
|
+
displayName ??= tag.argumentText;
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
return {
|
|
1950
|
+
...displayName !== void 0 && { displayName },
|
|
1951
|
+
memberDisplayNames
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1954
|
+
function extractBlockText(block) {
|
|
1955
|
+
return extractPlainText(block.content);
|
|
1956
|
+
}
|
|
1957
|
+
function extractPlainText(node) {
|
|
1958
|
+
let result = "";
|
|
1959
|
+
if (node instanceof DocPlainText) {
|
|
1960
|
+
return node.text;
|
|
1961
|
+
}
|
|
1962
|
+
if (node instanceof DocSoftBreak) {
|
|
1963
|
+
return " ";
|
|
1964
|
+
}
|
|
1965
|
+
if (typeof node.getChildNodes === "function") {
|
|
1966
|
+
for (const child of node.getChildNodes()) {
|
|
1967
|
+
result += extractPlainText(child);
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
return result;
|
|
1971
|
+
}
|
|
1972
|
+
function choosePreferredPayloadText(primary, fallback) {
|
|
1973
|
+
const preferred = primary.trim();
|
|
1974
|
+
const alternate = fallback.trim();
|
|
1975
|
+
if (preferred === "") return alternate;
|
|
1976
|
+
if (alternate === "") return preferred;
|
|
1977
|
+
if (alternate.includes("\n")) return alternate;
|
|
1978
|
+
if (alternate.length > preferred.length && alternate.startsWith(preferred)) {
|
|
1979
|
+
return alternate;
|
|
1980
|
+
}
|
|
1981
|
+
return preferred;
|
|
1982
|
+
}
|
|
1983
|
+
function getSharedPayloadText(tag, commentText, commentOffset) {
|
|
1984
|
+
if (tag.payloadSpan === null) {
|
|
1985
|
+
return "";
|
|
1986
|
+
}
|
|
1987
|
+
return sliceCommentSpan(commentText, tag.payloadSpan, {
|
|
1988
|
+
offset: commentOffset
|
|
1989
|
+
}).trim();
|
|
1990
|
+
}
|
|
1991
|
+
function getBestBlockPayloadText(tag, commentText, commentOffset, block) {
|
|
1992
|
+
const sharedText = tag === null ? "" : getSharedPayloadText(tag, commentText, commentOffset);
|
|
1993
|
+
const blockText = extractBlockText(block).replace(/\s+/g, " ").trim();
|
|
1994
|
+
return choosePreferredPayloadText(sharedText, blockText);
|
|
1995
|
+
}
|
|
1996
|
+
function collectRawTextFallbacks(node, file) {
|
|
1997
|
+
const fallbacks = /* @__PURE__ */ new Map();
|
|
1998
|
+
for (const tag of ts.getJSDocTags(node)) {
|
|
1999
|
+
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
2000
|
+
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
2001
|
+
const commentText = getTagCommentText(tag)?.trim() ?? "";
|
|
2002
|
+
if (commentText === "") continue;
|
|
2003
|
+
const entries = fallbacks.get(tagName) ?? [];
|
|
2004
|
+
entries.push({
|
|
2005
|
+
text: commentText,
|
|
2006
|
+
provenance: provenanceForJSDocTag(tag, file)
|
|
2007
|
+
});
|
|
2008
|
+
fallbacks.set(tagName, entries);
|
|
2009
|
+
}
|
|
2010
|
+
return fallbacks;
|
|
2011
|
+
}
|
|
2012
|
+
function isMemberTargetDisplayName(text) {
|
|
2013
|
+
return parseTagSyntax("displayName", text).target !== null;
|
|
2014
|
+
}
|
|
2015
|
+
function provenanceForComment(range, sourceFile, file, tagName) {
|
|
2016
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
|
|
2017
|
+
return {
|
|
2018
|
+
surface: "tsdoc",
|
|
2019
|
+
file,
|
|
2020
|
+
line: line + 1,
|
|
2021
|
+
column: character,
|
|
2022
|
+
tagName: "@" + tagName
|
|
2023
|
+
};
|
|
2024
|
+
}
|
|
2025
|
+
function provenanceForParsedTag(tag, sourceFile, file) {
|
|
2026
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(tag.tagNameSpan.start);
|
|
2027
|
+
return {
|
|
2028
|
+
surface: "tsdoc",
|
|
2029
|
+
file,
|
|
2030
|
+
line: line + 1,
|
|
2031
|
+
column: character,
|
|
2032
|
+
tagName: "@" + tag.normalizedTagName
|
|
2033
|
+
};
|
|
2034
|
+
}
|
|
2035
|
+
function provenanceForJSDocTag(tag, file) {
|
|
2036
|
+
const sourceFile = tag.getSourceFile();
|
|
2037
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(tag.getStart());
|
|
2038
|
+
return {
|
|
2039
|
+
surface: "tsdoc",
|
|
2040
|
+
file,
|
|
2041
|
+
line: line + 1,
|
|
2042
|
+
column: character,
|
|
2043
|
+
tagName: "@" + tag.tagName.text
|
|
2044
|
+
};
|
|
2045
|
+
}
|
|
2046
|
+
function getTagCommentText(tag) {
|
|
2047
|
+
if (tag.comment === void 0) {
|
|
2048
|
+
return void 0;
|
|
2049
|
+
}
|
|
2050
|
+
if (typeof tag.comment === "string") {
|
|
2051
|
+
return tag.comment;
|
|
2052
|
+
}
|
|
2053
|
+
return ts.getTextOfJSDocComment(tag.comment);
|
|
2054
|
+
}
|
|
2055
|
+
var TAGS_REQUIRING_RAW_TEXT, SYNTHETIC_TYPE_FORMAT_FLAGS, parserCache, parseResultCache;
|
|
2056
|
+
var init_tsdoc_parser = __esm({
|
|
2057
|
+
"src/analyzer/tsdoc-parser.ts"() {
|
|
2058
|
+
"use strict";
|
|
2059
|
+
TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
|
|
2060
|
+
SYNTHETIC_TYPE_FORMAT_FLAGS = ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope;
|
|
2061
|
+
parserCache = /* @__PURE__ */ new Map();
|
|
2062
|
+
parseResultCache = /* @__PURE__ */ new Map();
|
|
2063
|
+
}
|
|
2064
|
+
});
|
|
2065
|
+
|
|
2066
|
+
// src/analyzer/jsdoc-constraints.ts
|
|
2067
|
+
import * as ts2 from "typescript";
|
|
2068
|
+
function extractJSDocParseResult(node, file = "", options) {
|
|
2069
|
+
return parseTSDocTags(node, file, options);
|
|
2070
|
+
}
|
|
2071
|
+
function extractJSDocConstraintNodes(node, file = "", options) {
|
|
2072
|
+
const result = extractJSDocParseResult(node, file, options);
|
|
2073
|
+
return [...result.constraints];
|
|
2074
|
+
}
|
|
2075
|
+
function extractJSDocAnnotationNodes(node, file = "", options) {
|
|
2076
|
+
const result = extractJSDocParseResult(node, file, options);
|
|
2077
|
+
return [...result.annotations];
|
|
2078
|
+
}
|
|
2079
|
+
function extractDefaultValueAnnotation(initializer, file = "") {
|
|
2080
|
+
if (!initializer) return null;
|
|
2081
|
+
let value;
|
|
2082
|
+
if (ts2.isStringLiteral(initializer)) {
|
|
2083
|
+
value = initializer.text;
|
|
2084
|
+
} else if (ts2.isNumericLiteral(initializer)) {
|
|
2085
|
+
value = Number(initializer.text);
|
|
2086
|
+
} else if (initializer.kind === ts2.SyntaxKind.TrueKeyword) {
|
|
2087
|
+
value = true;
|
|
2088
|
+
} else if (initializer.kind === ts2.SyntaxKind.FalseKeyword) {
|
|
2089
|
+
value = false;
|
|
2090
|
+
} else if (initializer.kind === ts2.SyntaxKind.NullKeyword) {
|
|
2091
|
+
value = null;
|
|
2092
|
+
} else if (ts2.isPrefixUnaryExpression(initializer)) {
|
|
2093
|
+
if (initializer.operator === ts2.SyntaxKind.MinusToken && ts2.isNumericLiteral(initializer.operand)) {
|
|
2094
|
+
value = -Number(initializer.operand.text);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
if (value === void 0) return null;
|
|
2098
|
+
const sourceFile = initializer.getSourceFile();
|
|
2099
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(initializer.getStart());
|
|
2100
|
+
return {
|
|
2101
|
+
kind: "annotation",
|
|
2102
|
+
annotationKind: "defaultValue",
|
|
2103
|
+
value,
|
|
2104
|
+
provenance: {
|
|
2105
|
+
surface: "tsdoc",
|
|
2106
|
+
file,
|
|
2107
|
+
line: line + 1,
|
|
2108
|
+
column: character
|
|
2109
|
+
}
|
|
2110
|
+
};
|
|
2111
|
+
}
|
|
2112
|
+
var init_jsdoc_constraints = __esm({
|
|
2113
|
+
"src/analyzer/jsdoc-constraints.ts"() {
|
|
2114
|
+
"use strict";
|
|
2115
|
+
init_tsdoc_parser();
|
|
2116
|
+
}
|
|
2117
|
+
});
|
|
2118
|
+
|
|
2119
|
+
// src/analyzer/class-analyzer.ts
|
|
2120
|
+
import * as ts3 from "typescript";
|
|
2121
|
+
function isObjectType(type) {
|
|
2122
|
+
return !!(type.flags & ts3.TypeFlags.Object);
|
|
2123
|
+
}
|
|
2124
|
+
function isTypeReference(type) {
|
|
2125
|
+
return !!(type.flags & ts3.TypeFlags.Object) && !!(type.objectFlags & ts3.ObjectFlags.Reference);
|
|
2126
|
+
}
|
|
2127
|
+
function makeParseOptions(extensionRegistry, fieldType, checker, subjectType, hostType) {
|
|
2128
|
+
if (extensionRegistry === void 0 && fieldType === void 0 && checker === void 0 && subjectType === void 0 && hostType === void 0) {
|
|
2129
|
+
return void 0;
|
|
2130
|
+
}
|
|
2131
|
+
return {
|
|
2132
|
+
...extensionRegistry !== void 0 && { extensionRegistry },
|
|
2133
|
+
...fieldType !== void 0 && { fieldType },
|
|
2134
|
+
...checker !== void 0 && { checker },
|
|
2135
|
+
...subjectType !== void 0 && { subjectType },
|
|
2136
|
+
...hostType !== void 0 && { hostType }
|
|
2137
|
+
};
|
|
2138
|
+
}
|
|
2139
|
+
function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
2140
|
+
const name = classDecl.name?.text ?? "AnonymousClass";
|
|
2141
|
+
const fields = [];
|
|
2142
|
+
const fieldLayouts = [];
|
|
2143
|
+
const typeRegistry = {};
|
|
2144
|
+
const diagnostics = [];
|
|
2145
|
+
const classType = checker.getTypeAtLocation(classDecl);
|
|
2146
|
+
const classDoc = extractJSDocParseResult(
|
|
2147
|
+
classDecl,
|
|
2148
|
+
file,
|
|
2149
|
+
makeParseOptions(extensionRegistry, void 0, checker, classType, classType)
|
|
2150
|
+
);
|
|
2151
|
+
const annotations = [...classDoc.annotations];
|
|
2152
|
+
diagnostics.push(...classDoc.diagnostics);
|
|
2153
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
2154
|
+
const instanceMethods = [];
|
|
2155
|
+
const staticMethods = [];
|
|
2156
|
+
for (const member of classDecl.members) {
|
|
2157
|
+
if (ts3.isPropertyDeclaration(member)) {
|
|
2158
|
+
const fieldNode = analyzeFieldToIR(
|
|
2159
|
+
member,
|
|
2160
|
+
checker,
|
|
2161
|
+
file,
|
|
2162
|
+
typeRegistry,
|
|
2163
|
+
visiting,
|
|
2164
|
+
diagnostics,
|
|
2165
|
+
classType,
|
|
2166
|
+
extensionRegistry
|
|
2167
|
+
);
|
|
2168
|
+
if (fieldNode) {
|
|
2169
|
+
fields.push(fieldNode);
|
|
2170
|
+
fieldLayouts.push({});
|
|
2171
|
+
}
|
|
2172
|
+
} else if (ts3.isMethodDeclaration(member)) {
|
|
2173
|
+
const methodInfo = analyzeMethod(member, checker);
|
|
2174
|
+
if (methodInfo) {
|
|
2175
|
+
const isStatic = member.modifiers?.some((m) => m.kind === ts3.SyntaxKind.StaticKeyword);
|
|
2176
|
+
if (isStatic) {
|
|
2177
|
+
staticMethods.push(methodInfo);
|
|
2178
|
+
} else {
|
|
2179
|
+
instanceMethods.push(methodInfo);
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
return {
|
|
2185
|
+
name,
|
|
2186
|
+
fields,
|
|
2187
|
+
fieldLayouts,
|
|
2188
|
+
typeRegistry,
|
|
2189
|
+
...annotations.length > 0 && { annotations },
|
|
2190
|
+
...diagnostics.length > 0 && { diagnostics },
|
|
2191
|
+
instanceMethods,
|
|
2192
|
+
staticMethods
|
|
2193
|
+
};
|
|
2194
|
+
}
|
|
2195
|
+
function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
|
|
2196
|
+
const name = interfaceDecl.name.text;
|
|
2197
|
+
const fields = [];
|
|
2198
|
+
const typeRegistry = {};
|
|
2199
|
+
const diagnostics = [];
|
|
2200
|
+
const interfaceType = checker.getTypeAtLocation(interfaceDecl);
|
|
2201
|
+
const interfaceDoc = extractJSDocParseResult(
|
|
2202
|
+
interfaceDecl,
|
|
2203
|
+
file,
|
|
2204
|
+
makeParseOptions(extensionRegistry, void 0, checker, interfaceType, interfaceType)
|
|
2205
|
+
);
|
|
2206
|
+
const annotations = [...interfaceDoc.annotations];
|
|
2207
|
+
diagnostics.push(...interfaceDoc.diagnostics);
|
|
2208
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
2209
|
+
for (const member of interfaceDecl.members) {
|
|
2210
|
+
if (ts3.isPropertySignature(member)) {
|
|
2211
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2212
|
+
member,
|
|
2213
|
+
checker,
|
|
2214
|
+
file,
|
|
2215
|
+
typeRegistry,
|
|
2216
|
+
visiting,
|
|
2217
|
+
diagnostics,
|
|
2218
|
+
interfaceType,
|
|
2219
|
+
extensionRegistry
|
|
2220
|
+
);
|
|
2221
|
+
if (fieldNode) {
|
|
2222
|
+
fields.push(fieldNode);
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
const fieldLayouts = fields.map(() => ({}));
|
|
2227
|
+
return {
|
|
2228
|
+
name,
|
|
2229
|
+
fields,
|
|
2230
|
+
fieldLayouts,
|
|
2231
|
+
typeRegistry,
|
|
2232
|
+
...annotations.length > 0 && { annotations },
|
|
2233
|
+
...diagnostics.length > 0 && { diagnostics },
|
|
2234
|
+
instanceMethods: [],
|
|
2235
|
+
staticMethods: []
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2238
|
+
function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
|
|
2239
|
+
if (!ts3.isTypeLiteralNode(typeAlias.type)) {
|
|
2240
|
+
const sourceFile = typeAlias.getSourceFile();
|
|
2241
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
|
|
2242
|
+
const kindDesc = ts3.SyntaxKind[typeAlias.type.kind] ?? "unknown";
|
|
2243
|
+
return {
|
|
2244
|
+
ok: false,
|
|
2245
|
+
error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
|
|
2246
|
+
};
|
|
2247
|
+
}
|
|
2248
|
+
const name = typeAlias.name.text;
|
|
2249
|
+
const fields = [];
|
|
2250
|
+
const typeRegistry = {};
|
|
2251
|
+
const diagnostics = [];
|
|
2252
|
+
const aliasType = checker.getTypeAtLocation(typeAlias);
|
|
2253
|
+
const typeAliasDoc = extractJSDocParseResult(
|
|
2254
|
+
typeAlias,
|
|
2255
|
+
file,
|
|
2256
|
+
makeParseOptions(extensionRegistry, void 0, checker, aliasType, aliasType)
|
|
2257
|
+
);
|
|
2258
|
+
const annotations = [...typeAliasDoc.annotations];
|
|
2259
|
+
diagnostics.push(...typeAliasDoc.diagnostics);
|
|
2260
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
2261
|
+
for (const member of typeAlias.type.members) {
|
|
2262
|
+
if (ts3.isPropertySignature(member)) {
|
|
2263
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2264
|
+
member,
|
|
2265
|
+
checker,
|
|
2266
|
+
file,
|
|
2267
|
+
typeRegistry,
|
|
2268
|
+
visiting,
|
|
2269
|
+
diagnostics,
|
|
2270
|
+
aliasType,
|
|
2271
|
+
extensionRegistry
|
|
2272
|
+
);
|
|
2273
|
+
if (fieldNode) {
|
|
2274
|
+
fields.push(fieldNode);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
return {
|
|
2279
|
+
ok: true,
|
|
2280
|
+
analysis: {
|
|
2281
|
+
name,
|
|
2282
|
+
fields,
|
|
2283
|
+
fieldLayouts: fields.map(() => ({})),
|
|
2284
|
+
typeRegistry,
|
|
2285
|
+
...annotations.length > 0 && { annotations },
|
|
2286
|
+
...diagnostics.length > 0 && { diagnostics },
|
|
2287
|
+
instanceMethods: [],
|
|
2288
|
+
staticMethods: []
|
|
2289
|
+
}
|
|
2290
|
+
};
|
|
2291
|
+
}
|
|
2292
|
+
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
|
|
2293
|
+
if (!ts3.isIdentifier(prop.name)) {
|
|
2294
|
+
return null;
|
|
2295
|
+
}
|
|
2296
|
+
const name = prop.name.text;
|
|
2297
|
+
const tsType = checker.getTypeAtLocation(prop);
|
|
2298
|
+
const optional = prop.questionToken !== void 0;
|
|
2299
|
+
const provenance = provenanceForNode(prop, file);
|
|
2300
|
+
let type = resolveTypeNode(
|
|
2301
|
+
tsType,
|
|
2302
|
+
checker,
|
|
2303
|
+
file,
|
|
2304
|
+
typeRegistry,
|
|
2305
|
+
visiting,
|
|
2306
|
+
prop,
|
|
2307
|
+
extensionRegistry,
|
|
2308
|
+
diagnostics
|
|
2309
|
+
);
|
|
2310
|
+
const constraints = [];
|
|
2311
|
+
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
2312
|
+
constraints.push(
|
|
2313
|
+
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
2314
|
+
);
|
|
2315
|
+
}
|
|
2316
|
+
const docResult = extractJSDocParseResult(
|
|
2317
|
+
prop,
|
|
2318
|
+
file,
|
|
2319
|
+
makeParseOptions(extensionRegistry, type, checker, tsType, hostType)
|
|
2320
|
+
);
|
|
2321
|
+
constraints.push(...docResult.constraints);
|
|
2322
|
+
diagnostics.push(...docResult.diagnostics);
|
|
2323
|
+
let annotations = [];
|
|
2324
|
+
annotations.push(...docResult.annotations);
|
|
2325
|
+
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
2326
|
+
if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
|
|
2327
|
+
annotations.push(defaultAnnotation);
|
|
2328
|
+
}
|
|
2329
|
+
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
2330
|
+
return {
|
|
2331
|
+
kind: "field",
|
|
2332
|
+
name,
|
|
2333
|
+
type,
|
|
2334
|
+
required: !optional,
|
|
2335
|
+
constraints,
|
|
2336
|
+
annotations,
|
|
2337
|
+
provenance
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2340
|
+
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
|
|
2341
|
+
if (!ts3.isIdentifier(prop.name)) {
|
|
2342
|
+
return null;
|
|
2343
|
+
}
|
|
2344
|
+
const name = prop.name.text;
|
|
2345
|
+
const tsType = checker.getTypeAtLocation(prop);
|
|
2346
|
+
const optional = prop.questionToken !== void 0;
|
|
2347
|
+
const provenance = provenanceForNode(prop, file);
|
|
2348
|
+
let type = resolveTypeNode(
|
|
2349
|
+
tsType,
|
|
2350
|
+
checker,
|
|
2351
|
+
file,
|
|
2352
|
+
typeRegistry,
|
|
2353
|
+
visiting,
|
|
2354
|
+
prop,
|
|
2355
|
+
extensionRegistry,
|
|
2356
|
+
diagnostics
|
|
2357
|
+
);
|
|
2358
|
+
const constraints = [];
|
|
2359
|
+
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
2360
|
+
constraints.push(
|
|
2361
|
+
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
2362
|
+
);
|
|
2363
|
+
}
|
|
2364
|
+
const docResult = extractJSDocParseResult(
|
|
2365
|
+
prop,
|
|
2366
|
+
file,
|
|
2367
|
+
makeParseOptions(extensionRegistry, type, checker, tsType, hostType)
|
|
2368
|
+
);
|
|
2369
|
+
constraints.push(...docResult.constraints);
|
|
2370
|
+
diagnostics.push(...docResult.diagnostics);
|
|
2371
|
+
let annotations = [];
|
|
2372
|
+
annotations.push(...docResult.annotations);
|
|
2373
|
+
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
2374
|
+
return {
|
|
2375
|
+
kind: "field",
|
|
2376
|
+
name,
|
|
2377
|
+
type,
|
|
2378
|
+
required: !optional,
|
|
2379
|
+
constraints,
|
|
2380
|
+
annotations,
|
|
2381
|
+
provenance
|
|
2382
|
+
};
|
|
2383
|
+
}
|
|
2384
|
+
function applyEnumMemberDisplayNames(type, annotations) {
|
|
2385
|
+
if (!annotations.some(
|
|
2386
|
+
(annotation) => annotation.annotationKind === "displayName" && annotation.value.trim().startsWith(":")
|
|
2387
|
+
)) {
|
|
2388
|
+
return { type, annotations: [...annotations] };
|
|
2389
|
+
}
|
|
2390
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
2391
|
+
const nextType = rewriteEnumDisplayNames(type, annotations, consumed);
|
|
2392
|
+
if (consumed.size === 0) {
|
|
2393
|
+
return { type, annotations: [...annotations] };
|
|
2394
|
+
}
|
|
2395
|
+
return {
|
|
2396
|
+
type: nextType,
|
|
2397
|
+
annotations: annotations.filter((annotation) => !consumed.has(annotation))
|
|
2398
|
+
};
|
|
2399
|
+
}
|
|
2400
|
+
function rewriteEnumDisplayNames(type, annotations, consumed) {
|
|
2401
|
+
switch (type.kind) {
|
|
2402
|
+
case "enum":
|
|
2403
|
+
return applyEnumMemberDisplayNamesToEnum(type, annotations, consumed);
|
|
2404
|
+
case "union": {
|
|
2405
|
+
return {
|
|
2406
|
+
...type,
|
|
2407
|
+
members: type.members.map(
|
|
2408
|
+
(member) => rewriteEnumDisplayNames(member, annotations, consumed)
|
|
2409
|
+
)
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
default:
|
|
2413
|
+
return type;
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
function applyEnumMemberDisplayNamesToEnum(type, annotations, consumed) {
|
|
2417
|
+
const displayNames = /* @__PURE__ */ new Map();
|
|
2418
|
+
for (const annotation of annotations) {
|
|
2419
|
+
if (annotation.annotationKind !== "displayName") continue;
|
|
2420
|
+
const parsed = parseEnumMemberDisplayName(annotation.value);
|
|
2421
|
+
if (!parsed) continue;
|
|
2422
|
+
consumed.add(annotation);
|
|
2423
|
+
const member = type.members.find((m) => String(m.value) === parsed.value);
|
|
2424
|
+
if (!member) continue;
|
|
2425
|
+
displayNames.set(String(member.value), parsed.label);
|
|
2426
|
+
}
|
|
2427
|
+
if (displayNames.size === 0) {
|
|
2428
|
+
return type;
|
|
2429
|
+
}
|
|
2430
|
+
return {
|
|
2431
|
+
...type,
|
|
2432
|
+
members: type.members.map((member) => {
|
|
2433
|
+
const displayName = displayNames.get(String(member.value));
|
|
2434
|
+
return displayName !== void 0 ? { ...member, displayName } : member;
|
|
2435
|
+
})
|
|
2436
|
+
};
|
|
2437
|
+
}
|
|
2438
|
+
function parseEnumMemberDisplayName(value) {
|
|
2439
|
+
const trimmed = value.trim();
|
|
2440
|
+
const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(trimmed);
|
|
2441
|
+
if (!match?.[1] || !match[2]) return null;
|
|
2442
|
+
const label = match[2].trim();
|
|
2443
|
+
if (label === "") return null;
|
|
2444
|
+
return { value: match[1], label };
|
|
2445
|
+
}
|
|
2446
|
+
function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
|
|
2447
|
+
if (sourceNode === void 0 || extensionRegistry === void 0) {
|
|
2448
|
+
return null;
|
|
2449
|
+
}
|
|
2450
|
+
const typeNode = extractTypeNodeFromSource(sourceNode);
|
|
2451
|
+
if (typeNode === void 0) {
|
|
2452
|
+
return null;
|
|
2453
|
+
}
|
|
2454
|
+
return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
|
|
2455
|
+
}
|
|
2456
|
+
function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
|
|
2457
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
2458
|
+
return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
|
|
2459
|
+
}
|
|
2460
|
+
const typeName = getTypeNodeRegistrationName(typeNode);
|
|
2461
|
+
if (typeName === null) {
|
|
2462
|
+
return null;
|
|
2463
|
+
}
|
|
2464
|
+
const registration = extensionRegistry.findTypeByName(typeName);
|
|
2465
|
+
if (registration !== void 0) {
|
|
2466
|
+
return {
|
|
2467
|
+
kind: "custom",
|
|
2468
|
+
typeId: `${registration.extensionId}/${registration.registration.typeName}`,
|
|
2469
|
+
payload: null
|
|
2470
|
+
};
|
|
2471
|
+
}
|
|
2472
|
+
if (ts3.isTypeReferenceNode(typeNode) && ts3.isIdentifier(typeNode.typeName)) {
|
|
2473
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2474
|
+
if (aliasDecl !== void 0) {
|
|
2475
|
+
return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
return null;
|
|
2479
|
+
}
|
|
2480
|
+
function extractTypeNodeFromSource(sourceNode) {
|
|
2481
|
+
if (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode) || ts3.isTypeAliasDeclaration(sourceNode)) {
|
|
2482
|
+
return sourceNode.type;
|
|
2483
|
+
}
|
|
2484
|
+
if (ts3.isTypeNode(sourceNode)) {
|
|
2485
|
+
return sourceNode;
|
|
2486
|
+
}
|
|
2487
|
+
return void 0;
|
|
2488
|
+
}
|
|
2489
|
+
function getTypeNodeRegistrationName(typeNode) {
|
|
2490
|
+
if (ts3.isTypeReferenceNode(typeNode)) {
|
|
2491
|
+
return ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
|
|
2492
|
+
}
|
|
2493
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
2494
|
+
return getTypeNodeRegistrationName(typeNode.type);
|
|
2495
|
+
}
|
|
2496
|
+
if (typeNode.kind === ts3.SyntaxKind.BigIntKeyword || typeNode.kind === ts3.SyntaxKind.StringKeyword || typeNode.kind === ts3.SyntaxKind.NumberKeyword || typeNode.kind === ts3.SyntaxKind.BooleanKeyword) {
|
|
2497
|
+
return typeNode.getText();
|
|
2498
|
+
}
|
|
2499
|
+
return null;
|
|
2500
|
+
}
|
|
2501
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2502
|
+
const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
|
|
2503
|
+
if (customType) {
|
|
2504
|
+
return customType;
|
|
2505
|
+
}
|
|
2506
|
+
const primitiveAlias = tryResolveNamedPrimitiveAlias(
|
|
2507
|
+
type,
|
|
2508
|
+
checker,
|
|
2509
|
+
file,
|
|
2510
|
+
typeRegistry,
|
|
2511
|
+
visiting,
|
|
2512
|
+
sourceNode,
|
|
2513
|
+
extensionRegistry,
|
|
2514
|
+
diagnostics
|
|
2515
|
+
);
|
|
2516
|
+
if (primitiveAlias) {
|
|
2517
|
+
return primitiveAlias;
|
|
2518
|
+
}
|
|
2519
|
+
if (type.flags & ts3.TypeFlags.String) {
|
|
2520
|
+
return { kind: "primitive", primitiveKind: "string" };
|
|
2521
|
+
}
|
|
2522
|
+
if (type.flags & ts3.TypeFlags.Number) {
|
|
2523
|
+
return { kind: "primitive", primitiveKind: "number" };
|
|
2524
|
+
}
|
|
2525
|
+
if (type.flags & (ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral)) {
|
|
2526
|
+
return { kind: "primitive", primitiveKind: "bigint" };
|
|
2527
|
+
}
|
|
2528
|
+
if (type.flags & ts3.TypeFlags.Boolean) {
|
|
2529
|
+
return { kind: "primitive", primitiveKind: "boolean" };
|
|
2530
|
+
}
|
|
2531
|
+
if (type.flags & ts3.TypeFlags.Null) {
|
|
2532
|
+
return { kind: "primitive", primitiveKind: "null" };
|
|
2533
|
+
}
|
|
2534
|
+
if (type.flags & ts3.TypeFlags.Undefined) {
|
|
2535
|
+
return { kind: "primitive", primitiveKind: "null" };
|
|
2536
|
+
}
|
|
2537
|
+
if (type.isStringLiteral()) {
|
|
2538
|
+
return {
|
|
2539
|
+
kind: "enum",
|
|
2540
|
+
members: [{ value: type.value }]
|
|
2541
|
+
};
|
|
2542
|
+
}
|
|
2543
|
+
if (type.isNumberLiteral()) {
|
|
2544
|
+
return {
|
|
2545
|
+
kind: "enum",
|
|
2546
|
+
members: [{ value: type.value }]
|
|
2547
|
+
};
|
|
2548
|
+
}
|
|
2549
|
+
if (type.isUnion()) {
|
|
2550
|
+
return resolveUnionType(
|
|
2551
|
+
type,
|
|
2552
|
+
checker,
|
|
2553
|
+
file,
|
|
2554
|
+
typeRegistry,
|
|
2555
|
+
visiting,
|
|
2556
|
+
sourceNode,
|
|
2557
|
+
extensionRegistry,
|
|
2558
|
+
diagnostics
|
|
2559
|
+
);
|
|
2560
|
+
}
|
|
2561
|
+
if (checker.isArrayType(type)) {
|
|
2562
|
+
return resolveArrayType(
|
|
2563
|
+
type,
|
|
2564
|
+
checker,
|
|
2565
|
+
file,
|
|
2566
|
+
typeRegistry,
|
|
2567
|
+
visiting,
|
|
2568
|
+
sourceNode,
|
|
2569
|
+
extensionRegistry,
|
|
2570
|
+
diagnostics
|
|
2571
|
+
);
|
|
2572
|
+
}
|
|
2573
|
+
if (isObjectType(type)) {
|
|
2574
|
+
return resolveObjectType(
|
|
2575
|
+
type,
|
|
2576
|
+
checker,
|
|
2577
|
+
file,
|
|
2578
|
+
typeRegistry,
|
|
2579
|
+
visiting,
|
|
2580
|
+
extensionRegistry,
|
|
2581
|
+
diagnostics
|
|
2582
|
+
);
|
|
2583
|
+
}
|
|
2584
|
+
return { kind: "primitive", primitiveKind: "string" };
|
|
2585
|
+
}
|
|
2586
|
+
function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2587
|
+
if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
|
|
2588
|
+
return null;
|
|
2589
|
+
}
|
|
2590
|
+
const aliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration) ?? getReferencedTypeAliasDeclaration(sourceNode, checker);
|
|
2591
|
+
if (!aliasDecl) {
|
|
2592
|
+
return null;
|
|
2593
|
+
}
|
|
2594
|
+
const aliasName = aliasDecl.name.text;
|
|
2595
|
+
if (!typeRegistry[aliasName]) {
|
|
2596
|
+
const aliasType = checker.getTypeFromTypeNode(aliasDecl.type);
|
|
2597
|
+
const constraints = [
|
|
2598
|
+
...extractJSDocConstraintNodes(aliasDecl, file, makeParseOptions(extensionRegistry)),
|
|
2599
|
+
...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry)
|
|
2600
|
+
];
|
|
2601
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
2602
|
+
aliasDecl,
|
|
2603
|
+
file,
|
|
2604
|
+
makeParseOptions(extensionRegistry)
|
|
2605
|
+
);
|
|
2606
|
+
typeRegistry[aliasName] = {
|
|
2607
|
+
name: aliasName,
|
|
2608
|
+
type: resolveAliasedPrimitiveTarget(
|
|
2609
|
+
aliasType,
|
|
2610
|
+
checker,
|
|
2611
|
+
file,
|
|
2612
|
+
typeRegistry,
|
|
2613
|
+
visiting,
|
|
2614
|
+
extensionRegistry,
|
|
2615
|
+
diagnostics
|
|
2616
|
+
),
|
|
2617
|
+
...constraints.length > 0 && { constraints },
|
|
2618
|
+
...annotations.length > 0 && { annotations },
|
|
2619
|
+
provenance: provenanceForDeclaration(aliasDecl, file)
|
|
2620
|
+
};
|
|
2621
|
+
}
|
|
2622
|
+
return { kind: "reference", name: aliasName, typeArguments: [] };
|
|
2623
|
+
}
|
|
2624
|
+
function getReferencedTypeAliasDeclaration(sourceNode, checker) {
|
|
2625
|
+
const typeNode = sourceNode && (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode)) ? sourceNode.type : void 0;
|
|
2626
|
+
if (!typeNode || !ts3.isTypeReferenceNode(typeNode)) {
|
|
2627
|
+
return void 0;
|
|
2628
|
+
}
|
|
2629
|
+
return checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2630
|
+
}
|
|
2631
|
+
function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
|
|
2632
|
+
if (!ts3.isTypeReferenceNode(typeNode)) {
|
|
2633
|
+
return false;
|
|
2634
|
+
}
|
|
2635
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2636
|
+
if (!aliasDecl) {
|
|
2637
|
+
return false;
|
|
2638
|
+
}
|
|
2639
|
+
const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
|
|
2640
|
+
return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
|
|
2641
|
+
}
|
|
2642
|
+
function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
2643
|
+
const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2644
|
+
if (nestedAliasDecl !== void 0) {
|
|
2645
|
+
return resolveAliasedPrimitiveTarget(
|
|
2646
|
+
checker.getTypeFromTypeNode(nestedAliasDecl.type),
|
|
2647
|
+
checker,
|
|
2648
|
+
file,
|
|
2649
|
+
typeRegistry,
|
|
2650
|
+
visiting,
|
|
2651
|
+
extensionRegistry,
|
|
2652
|
+
diagnostics
|
|
2653
|
+
);
|
|
2654
|
+
}
|
|
2655
|
+
return resolveTypeNode(
|
|
2656
|
+
type,
|
|
2657
|
+
checker,
|
|
2658
|
+
file,
|
|
2659
|
+
typeRegistry,
|
|
2660
|
+
visiting,
|
|
2661
|
+
void 0,
|
|
2662
|
+
extensionRegistry,
|
|
2663
|
+
diagnostics
|
|
2664
|
+
);
|
|
2665
|
+
}
|
|
2666
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2667
|
+
const typeName = getNamedTypeName(type);
|
|
2668
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
2669
|
+
if (typeName && typeName in typeRegistry) {
|
|
2670
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
2671
|
+
}
|
|
2672
|
+
const allTypes = type.types;
|
|
2673
|
+
const unionMemberTypeNodes = extractUnionMemberTypeNodes(sourceNode, checker);
|
|
2674
|
+
const nonNullSourceNodes = unionMemberTypeNodes.filter(
|
|
2675
|
+
(memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
|
|
2676
|
+
);
|
|
2677
|
+
const nonNullTypes = allTypes.filter(
|
|
2678
|
+
(memberType) => !(memberType.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
|
|
2679
|
+
);
|
|
2680
|
+
const nonNullMembers = nonNullTypes.map((memberType, index) => ({
|
|
2681
|
+
memberType,
|
|
2682
|
+
sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
|
|
2683
|
+
}));
|
|
2684
|
+
const hasNull = allTypes.some((t) => t.flags & ts3.TypeFlags.Null);
|
|
2685
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
2686
|
+
if (namedDecl) {
|
|
2687
|
+
for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
|
|
2688
|
+
memberDisplayNames.set(value, label);
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
if (sourceNode) {
|
|
2692
|
+
for (const [value, label] of extractDisplayNameMetadata(sourceNode).memberDisplayNames) {
|
|
2693
|
+
memberDisplayNames.set(value, label);
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
const registerNamed = (result) => {
|
|
2697
|
+
if (!typeName) {
|
|
2698
|
+
return result;
|
|
2699
|
+
}
|
|
2700
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2701
|
+
typeRegistry[typeName] = {
|
|
2702
|
+
name: typeName,
|
|
2703
|
+
type: result,
|
|
2704
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2705
|
+
provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
|
|
2706
|
+
};
|
|
2707
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
2708
|
+
};
|
|
2709
|
+
const applyMemberLabels = (members2) => members2.map((value) => {
|
|
2710
|
+
const displayName = memberDisplayNames.get(String(value));
|
|
2711
|
+
return displayName !== void 0 ? { value, displayName } : { value };
|
|
2712
|
+
});
|
|
2713
|
+
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts3.TypeFlags.BooleanLiteral);
|
|
2714
|
+
if (isBooleanUnion2) {
|
|
2715
|
+
const boolNode = { kind: "primitive", primitiveKind: "boolean" };
|
|
2716
|
+
const result = hasNull ? {
|
|
2717
|
+
kind: "union",
|
|
2718
|
+
members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2719
|
+
} : boolNode;
|
|
2720
|
+
return registerNamed(result);
|
|
2721
|
+
}
|
|
2722
|
+
const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
|
|
2723
|
+
if (allStringLiterals && nonNullTypes.length > 0) {
|
|
2724
|
+
const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
|
|
2725
|
+
const enumNode = {
|
|
2726
|
+
kind: "enum",
|
|
2727
|
+
members: applyMemberLabels(stringTypes.map((t) => t.value))
|
|
2728
|
+
};
|
|
2729
|
+
const result = hasNull ? {
|
|
2730
|
+
kind: "union",
|
|
2731
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2732
|
+
} : enumNode;
|
|
2733
|
+
return registerNamed(result);
|
|
2734
|
+
}
|
|
2735
|
+
const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
|
|
2736
|
+
if (allNumberLiterals && nonNullTypes.length > 0) {
|
|
2737
|
+
const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
|
|
2738
|
+
const enumNode = {
|
|
2739
|
+
kind: "enum",
|
|
2740
|
+
members: applyMemberLabels(numberTypes.map((t) => t.value))
|
|
2741
|
+
};
|
|
2742
|
+
const result = hasNull ? {
|
|
2743
|
+
kind: "union",
|
|
2744
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2745
|
+
} : enumNode;
|
|
2746
|
+
return registerNamed(result);
|
|
2747
|
+
}
|
|
2748
|
+
if (nonNullMembers.length === 1 && nonNullMembers[0]) {
|
|
2749
|
+
const inner = resolveTypeNode(
|
|
2750
|
+
nonNullMembers[0].memberType,
|
|
2751
|
+
checker,
|
|
2752
|
+
file,
|
|
2753
|
+
typeRegistry,
|
|
2754
|
+
visiting,
|
|
2755
|
+
nonNullMembers[0].sourceNode ?? sourceNode,
|
|
2756
|
+
extensionRegistry,
|
|
2757
|
+
diagnostics
|
|
2758
|
+
);
|
|
2759
|
+
const result = hasNull ? {
|
|
2760
|
+
kind: "union",
|
|
2761
|
+
members: [inner, { kind: "primitive", primitiveKind: "null" }]
|
|
2762
|
+
} : inner;
|
|
2763
|
+
return registerNamed(result);
|
|
2764
|
+
}
|
|
2765
|
+
const members = nonNullMembers.map(
|
|
2766
|
+
({ memberType, sourceNode: memberSourceNode }) => resolveTypeNode(
|
|
2767
|
+
memberType,
|
|
2768
|
+
checker,
|
|
2769
|
+
file,
|
|
2770
|
+
typeRegistry,
|
|
2771
|
+
visiting,
|
|
2772
|
+
memberSourceNode ?? sourceNode,
|
|
2773
|
+
extensionRegistry,
|
|
2774
|
+
diagnostics
|
|
2775
|
+
)
|
|
2776
|
+
);
|
|
2777
|
+
if (hasNull) {
|
|
2778
|
+
members.push({ kind: "primitive", primitiveKind: "null" });
|
|
2779
|
+
}
|
|
2780
|
+
return registerNamed({ kind: "union", members });
|
|
2781
|
+
}
|
|
2782
|
+
function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2783
|
+
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
2784
|
+
const elementType = typeArgs?.[0];
|
|
2785
|
+
const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
|
|
2786
|
+
const items = elementType ? resolveTypeNode(
|
|
2787
|
+
elementType,
|
|
2788
|
+
checker,
|
|
2789
|
+
file,
|
|
2790
|
+
typeRegistry,
|
|
2791
|
+
visiting,
|
|
2792
|
+
elementSourceNode,
|
|
2793
|
+
extensionRegistry,
|
|
2794
|
+
diagnostics
|
|
2795
|
+
) : { kind: "primitive", primitiveKind: "string" };
|
|
2796
|
+
return { kind: "array", items };
|
|
2797
|
+
}
|
|
2798
|
+
function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
2799
|
+
if (type.getProperties().length > 0) {
|
|
2800
|
+
return null;
|
|
2801
|
+
}
|
|
2802
|
+
const indexInfo = checker.getIndexInfoOfType(type, ts3.IndexKind.String);
|
|
2803
|
+
if (!indexInfo) {
|
|
2804
|
+
return null;
|
|
2805
|
+
}
|
|
2806
|
+
const valueType = resolveTypeNode(
|
|
2807
|
+
indexInfo.type,
|
|
2808
|
+
checker,
|
|
2809
|
+
file,
|
|
2810
|
+
typeRegistry,
|
|
2811
|
+
visiting,
|
|
2812
|
+
void 0,
|
|
2813
|
+
extensionRegistry,
|
|
2814
|
+
diagnostics
|
|
2815
|
+
);
|
|
2816
|
+
return { kind: "record", valueType };
|
|
2817
|
+
}
|
|
2818
|
+
function typeNodeContainsReference(type, targetName) {
|
|
2819
|
+
switch (type.kind) {
|
|
2820
|
+
case "reference":
|
|
2821
|
+
return type.name === targetName;
|
|
2822
|
+
case "array":
|
|
2823
|
+
return typeNodeContainsReference(type.items, targetName);
|
|
2824
|
+
case "record":
|
|
2825
|
+
return typeNodeContainsReference(type.valueType, targetName);
|
|
2826
|
+
case "union":
|
|
2827
|
+
return type.members.some((member) => typeNodeContainsReference(member, targetName));
|
|
2828
|
+
case "object":
|
|
2829
|
+
return type.properties.some(
|
|
2830
|
+
(property) => typeNodeContainsReference(property.type, targetName)
|
|
2831
|
+
);
|
|
2832
|
+
case "primitive":
|
|
2833
|
+
case "enum":
|
|
2834
|
+
case "dynamic":
|
|
2835
|
+
case "custom":
|
|
2836
|
+
return false;
|
|
2837
|
+
default: {
|
|
2838
|
+
const _exhaustive = type;
|
|
2839
|
+
return _exhaustive;
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
2844
|
+
const typeName = getNamedTypeName(type);
|
|
2845
|
+
const namedTypeName = typeName ?? void 0;
|
|
2846
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
2847
|
+
const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
|
|
2848
|
+
const clearNamedTypeRegistration = () => {
|
|
2849
|
+
if (namedTypeName === void 0 || !shouldRegisterNamedType) {
|
|
2850
|
+
return;
|
|
2851
|
+
}
|
|
2852
|
+
Reflect.deleteProperty(typeRegistry, namedTypeName);
|
|
2853
|
+
};
|
|
2854
|
+
if (visiting.has(type)) {
|
|
2855
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2856
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2857
|
+
}
|
|
2858
|
+
return { kind: "object", properties: [], additionalProperties: false };
|
|
2859
|
+
}
|
|
2860
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
|
|
2861
|
+
typeRegistry[namedTypeName] = {
|
|
2862
|
+
name: namedTypeName,
|
|
2863
|
+
type: RESOLVING_TYPE_PLACEHOLDER,
|
|
2864
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2865
|
+
};
|
|
2866
|
+
}
|
|
2867
|
+
visiting.add(type);
|
|
2868
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
|
|
2869
|
+
if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
|
|
2870
|
+
visiting.delete(type);
|
|
2871
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
const recordNode = tryResolveRecordType(
|
|
2875
|
+
type,
|
|
2876
|
+
checker,
|
|
2877
|
+
file,
|
|
2878
|
+
typeRegistry,
|
|
2879
|
+
visiting,
|
|
2880
|
+
extensionRegistry,
|
|
2881
|
+
diagnostics
|
|
2882
|
+
);
|
|
2883
|
+
if (recordNode) {
|
|
2884
|
+
visiting.delete(type);
|
|
2885
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2886
|
+
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
|
|
2887
|
+
if (!isRecursiveRecord) {
|
|
2888
|
+
clearNamedTypeRegistration();
|
|
2889
|
+
return recordNode;
|
|
2890
|
+
}
|
|
2891
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2892
|
+
typeRegistry[namedTypeName] = {
|
|
2893
|
+
name: namedTypeName,
|
|
2894
|
+
type: recordNode,
|
|
2895
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2896
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2897
|
+
};
|
|
2898
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2899
|
+
}
|
|
2900
|
+
return recordNode;
|
|
2901
|
+
}
|
|
2902
|
+
const properties = [];
|
|
2903
|
+
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
|
|
2904
|
+
type,
|
|
2905
|
+
checker,
|
|
2906
|
+
file,
|
|
2907
|
+
typeRegistry,
|
|
2908
|
+
visiting,
|
|
2909
|
+
diagnostics ?? [],
|
|
2910
|
+
extensionRegistry
|
|
2911
|
+
);
|
|
2912
|
+
for (const prop of type.getProperties()) {
|
|
2913
|
+
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
2914
|
+
if (!declaration) continue;
|
|
2915
|
+
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
2916
|
+
const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
|
|
2917
|
+
const propTypeNode = resolveTypeNode(
|
|
2918
|
+
propType,
|
|
2919
|
+
checker,
|
|
2920
|
+
file,
|
|
2921
|
+
typeRegistry,
|
|
2922
|
+
visiting,
|
|
2923
|
+
declaration,
|
|
2924
|
+
extensionRegistry,
|
|
2925
|
+
diagnostics
|
|
2926
|
+
);
|
|
2927
|
+
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
2928
|
+
properties.push({
|
|
2929
|
+
name: prop.name,
|
|
2930
|
+
type: propTypeNode,
|
|
2931
|
+
optional,
|
|
2932
|
+
constraints: fieldNodeInfo?.constraints ?? [],
|
|
2933
|
+
annotations: fieldNodeInfo?.annotations ?? [],
|
|
2934
|
+
provenance: fieldNodeInfo?.provenance ?? provenanceForFile(file)
|
|
2935
|
+
});
|
|
2936
|
+
}
|
|
2937
|
+
visiting.delete(type);
|
|
2938
|
+
const objectNode = {
|
|
2939
|
+
kind: "object",
|
|
2940
|
+
properties,
|
|
2941
|
+
additionalProperties: true
|
|
2942
|
+
};
|
|
2943
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2944
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2945
|
+
typeRegistry[namedTypeName] = {
|
|
2946
|
+
name: namedTypeName,
|
|
2947
|
+
type: objectNode,
|
|
2948
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2949
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2950
|
+
};
|
|
2951
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2952
|
+
}
|
|
2953
|
+
return objectNode;
|
|
2954
|
+
}
|
|
2955
|
+
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, diagnostics, extensionRegistry) {
|
|
2956
|
+
const symbols = [type.getSymbol(), type.aliasSymbol].filter(
|
|
2957
|
+
(s) => s?.declarations != null && s.declarations.length > 0
|
|
2958
|
+
);
|
|
2959
|
+
for (const symbol of symbols) {
|
|
2960
|
+
const declarations = symbol.declarations;
|
|
2961
|
+
if (!declarations) continue;
|
|
2962
|
+
const classDecl = declarations.find(ts3.isClassDeclaration);
|
|
2963
|
+
if (classDecl) {
|
|
2964
|
+
const map = /* @__PURE__ */ new Map();
|
|
2965
|
+
const hostType = checker.getTypeAtLocation(classDecl);
|
|
2966
|
+
for (const member of classDecl.members) {
|
|
2967
|
+
if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name)) {
|
|
2968
|
+
const fieldNode = analyzeFieldToIR(
|
|
2969
|
+
member,
|
|
2970
|
+
checker,
|
|
2971
|
+
file,
|
|
2972
|
+
typeRegistry,
|
|
2973
|
+
visiting,
|
|
2974
|
+
diagnostics,
|
|
2975
|
+
hostType,
|
|
2976
|
+
extensionRegistry
|
|
2977
|
+
);
|
|
2978
|
+
if (fieldNode) {
|
|
2979
|
+
map.set(fieldNode.name, {
|
|
2980
|
+
constraints: [...fieldNode.constraints],
|
|
2981
|
+
annotations: [...fieldNode.annotations],
|
|
2982
|
+
provenance: fieldNode.provenance
|
|
2983
|
+
});
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
return map;
|
|
2988
|
+
}
|
|
2989
|
+
const interfaceDecl = declarations.find(ts3.isInterfaceDeclaration);
|
|
2990
|
+
if (interfaceDecl) {
|
|
2991
|
+
return buildFieldNodeInfoMap(
|
|
2992
|
+
interfaceDecl.members,
|
|
2993
|
+
checker,
|
|
2994
|
+
file,
|
|
2995
|
+
typeRegistry,
|
|
2996
|
+
visiting,
|
|
2997
|
+
checker.getTypeAtLocation(interfaceDecl),
|
|
2998
|
+
diagnostics,
|
|
2999
|
+
extensionRegistry
|
|
3000
|
+
);
|
|
3001
|
+
}
|
|
3002
|
+
const typeAliasDecl = declarations.find(ts3.isTypeAliasDeclaration);
|
|
3003
|
+
if (typeAliasDecl && ts3.isTypeLiteralNode(typeAliasDecl.type)) {
|
|
3004
|
+
return buildFieldNodeInfoMap(
|
|
3005
|
+
typeAliasDecl.type.members,
|
|
3006
|
+
checker,
|
|
3007
|
+
file,
|
|
3008
|
+
typeRegistry,
|
|
3009
|
+
visiting,
|
|
3010
|
+
checker.getTypeAtLocation(typeAliasDecl),
|
|
3011
|
+
diagnostics,
|
|
3012
|
+
extensionRegistry
|
|
3013
|
+
);
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
return null;
|
|
3017
|
+
}
|
|
3018
|
+
function extractArrayElementTypeNode(sourceNode, checker) {
|
|
3019
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
3020
|
+
if (typeNode === void 0) {
|
|
3021
|
+
return void 0;
|
|
3022
|
+
}
|
|
3023
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
3024
|
+
if (ts3.isArrayTypeNode(resolvedTypeNode)) {
|
|
3025
|
+
return resolvedTypeNode.elementType;
|
|
3026
|
+
}
|
|
3027
|
+
if (ts3.isTypeReferenceNode(resolvedTypeNode) && ts3.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
|
|
3028
|
+
return resolvedTypeNode.typeArguments[0];
|
|
3029
|
+
}
|
|
3030
|
+
return void 0;
|
|
3031
|
+
}
|
|
3032
|
+
function extractUnionMemberTypeNodes(sourceNode, checker) {
|
|
3033
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
3034
|
+
if (!typeNode) {
|
|
3035
|
+
return [];
|
|
3036
|
+
}
|
|
3037
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
3038
|
+
return ts3.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
|
|
3039
|
+
}
|
|
3040
|
+
function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
|
|
3041
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
3042
|
+
return resolveAliasedTypeNode(typeNode.type, checker, visited);
|
|
3043
|
+
}
|
|
3044
|
+
if (!ts3.isTypeReferenceNode(typeNode) || !ts3.isIdentifier(typeNode.typeName)) {
|
|
3045
|
+
return typeNode;
|
|
3046
|
+
}
|
|
3047
|
+
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
3048
|
+
const aliasDecl = symbol?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
3049
|
+
if (aliasDecl === void 0 || visited.has(aliasDecl)) {
|
|
3050
|
+
return typeNode;
|
|
3051
|
+
}
|
|
3052
|
+
visited.add(aliasDecl);
|
|
3053
|
+
return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
|
|
3054
|
+
}
|
|
3055
|
+
function isNullishTypeNode(typeNode) {
|
|
3056
|
+
if (typeNode.kind === ts3.SyntaxKind.NullKeyword || typeNode.kind === ts3.SyntaxKind.UndefinedKeyword) {
|
|
3057
|
+
return true;
|
|
3058
|
+
}
|
|
3059
|
+
return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
|
|
3060
|
+
}
|
|
3061
|
+
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, hostType, diagnostics, extensionRegistry) {
|
|
3062
|
+
const map = /* @__PURE__ */ new Map();
|
|
3063
|
+
for (const member of members) {
|
|
3064
|
+
if (ts3.isPropertySignature(member)) {
|
|
3065
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
3066
|
+
member,
|
|
3067
|
+
checker,
|
|
3068
|
+
file,
|
|
3069
|
+
typeRegistry,
|
|
3070
|
+
visiting,
|
|
3071
|
+
diagnostics,
|
|
3072
|
+
hostType,
|
|
3073
|
+
extensionRegistry
|
|
3074
|
+
);
|
|
3075
|
+
if (fieldNode) {
|
|
3076
|
+
map.set(fieldNode.name, {
|
|
3077
|
+
constraints: [...fieldNode.constraints],
|
|
3078
|
+
annotations: [...fieldNode.annotations],
|
|
3079
|
+
provenance: fieldNode.provenance
|
|
3080
|
+
});
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
return map;
|
|
3085
|
+
}
|
|
3086
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
|
|
3087
|
+
if (!ts3.isTypeReferenceNode(typeNode)) return [];
|
|
3088
|
+
if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
|
|
3089
|
+
const aliasName = typeNode.typeName.getText();
|
|
3090
|
+
throw new Error(
|
|
3091
|
+
`Type alias chain exceeds maximum depth of ${String(MAX_ALIAS_CHAIN_DEPTH)} at alias "${aliasName}" in ${file}. Simplify the alias chain or check for circular references.`
|
|
3092
|
+
);
|
|
3093
|
+
}
|
|
3094
|
+
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
3095
|
+
if (!symbol?.declarations) return [];
|
|
3096
|
+
const aliasDecl = symbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
3097
|
+
if (!aliasDecl) return [];
|
|
3098
|
+
if (ts3.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
3099
|
+
const aliasFieldType = resolveTypeNode(
|
|
3100
|
+
checker.getTypeAtLocation(aliasDecl.type),
|
|
3101
|
+
checker,
|
|
3102
|
+
file,
|
|
3103
|
+
{},
|
|
3104
|
+
/* @__PURE__ */ new Set(),
|
|
3105
|
+
aliasDecl.type,
|
|
3106
|
+
extensionRegistry
|
|
3107
|
+
);
|
|
3108
|
+
const constraints = extractJSDocConstraintNodes(
|
|
3109
|
+
aliasDecl,
|
|
3110
|
+
file,
|
|
3111
|
+
makeParseOptions(extensionRegistry, aliasFieldType)
|
|
3112
|
+
);
|
|
3113
|
+
constraints.push(
|
|
3114
|
+
...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry, depth + 1)
|
|
3115
|
+
);
|
|
3116
|
+
return constraints;
|
|
3117
|
+
}
|
|
3118
|
+
function provenanceForNode(node, file) {
|
|
3119
|
+
const sourceFile = node.getSourceFile();
|
|
3120
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
3121
|
+
return {
|
|
3122
|
+
surface: "tsdoc",
|
|
3123
|
+
file,
|
|
3124
|
+
line: line + 1,
|
|
3125
|
+
column: character
|
|
3126
|
+
};
|
|
3127
|
+
}
|
|
3128
|
+
function provenanceForFile(file) {
|
|
3129
|
+
return { surface: "tsdoc", file, line: 0, column: 0 };
|
|
3130
|
+
}
|
|
3131
|
+
function provenanceForDeclaration(node, file) {
|
|
3132
|
+
if (!node) {
|
|
3133
|
+
return provenanceForFile(file);
|
|
3134
|
+
}
|
|
3135
|
+
return provenanceForNode(node, file);
|
|
3136
|
+
}
|
|
3137
|
+
function getNamedTypeName(type) {
|
|
3138
|
+
const symbol = type.getSymbol();
|
|
3139
|
+
if (symbol?.declarations) {
|
|
3140
|
+
const decl = symbol.declarations[0];
|
|
3141
|
+
if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
|
|
3142
|
+
const name = ts3.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
|
|
3143
|
+
if (name) return name;
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
const aliasSymbol = type.aliasSymbol;
|
|
3147
|
+
if (aliasSymbol?.declarations) {
|
|
3148
|
+
const aliasDecl = aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
3149
|
+
if (aliasDecl) {
|
|
3150
|
+
return aliasDecl.name.text;
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
return null;
|
|
3154
|
+
}
|
|
3155
|
+
function getNamedTypeDeclaration(type) {
|
|
3156
|
+
const symbol = type.getSymbol();
|
|
3157
|
+
if (symbol?.declarations) {
|
|
3158
|
+
const decl = symbol.declarations[0];
|
|
3159
|
+
if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
|
|
3160
|
+
return decl;
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
const aliasSymbol = type.aliasSymbol;
|
|
3164
|
+
if (aliasSymbol?.declarations) {
|
|
3165
|
+
return aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
3166
|
+
}
|
|
3167
|
+
return void 0;
|
|
3168
|
+
}
|
|
3169
|
+
function analyzeMethod(method, checker) {
|
|
3170
|
+
if (!ts3.isIdentifier(method.name)) {
|
|
3171
|
+
return null;
|
|
3172
|
+
}
|
|
3173
|
+
const name = method.name.text;
|
|
3174
|
+
const parameters = [];
|
|
3175
|
+
for (const param of method.parameters) {
|
|
3176
|
+
if (ts3.isIdentifier(param.name)) {
|
|
3177
|
+
const paramInfo = analyzeParameter(param, checker);
|
|
3178
|
+
parameters.push(paramInfo);
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
const returnTypeNode = method.type;
|
|
3182
|
+
const signature = checker.getSignatureFromDeclaration(method);
|
|
3183
|
+
const returnType = signature ? checker.getReturnTypeOfSignature(signature) : checker.getTypeAtLocation(method);
|
|
3184
|
+
return { name, parameters, returnTypeNode, returnType };
|
|
3185
|
+
}
|
|
3186
|
+
function analyzeParameter(param, checker) {
|
|
3187
|
+
const name = ts3.isIdentifier(param.name) ? param.name.text : "param";
|
|
3188
|
+
const typeNode = param.type;
|
|
3189
|
+
const type = checker.getTypeAtLocation(param);
|
|
3190
|
+
const formSpecExportName = detectFormSpecReference(typeNode);
|
|
3191
|
+
const optional = param.questionToken !== void 0 || param.initializer !== void 0;
|
|
3192
|
+
return { name, typeNode, type, formSpecExportName, optional };
|
|
3193
|
+
}
|
|
3194
|
+
function detectFormSpecReference(typeNode) {
|
|
3195
|
+
if (!typeNode) return null;
|
|
3196
|
+
if (!ts3.isTypeReferenceNode(typeNode)) return null;
|
|
3197
|
+
const typeName = ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts3.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
|
|
3198
|
+
if (typeName !== "InferSchema" && typeName !== "InferFormSchema") return null;
|
|
3199
|
+
const typeArg = typeNode.typeArguments?.[0];
|
|
3200
|
+
if (!typeArg || !ts3.isTypeQueryNode(typeArg)) return null;
|
|
3201
|
+
if (ts3.isIdentifier(typeArg.exprName)) {
|
|
3202
|
+
return typeArg.exprName.text;
|
|
3203
|
+
}
|
|
3204
|
+
if (ts3.isQualifiedName(typeArg.exprName)) {
|
|
3205
|
+
return typeArg.exprName.right.text;
|
|
3206
|
+
}
|
|
3207
|
+
return null;
|
|
3208
|
+
}
|
|
3209
|
+
var RESOLVING_TYPE_PLACEHOLDER, MAX_ALIAS_CHAIN_DEPTH;
|
|
3210
|
+
var init_class_analyzer = __esm({
|
|
3211
|
+
"src/analyzer/class-analyzer.ts"() {
|
|
3212
|
+
"use strict";
|
|
3213
|
+
init_jsdoc_constraints();
|
|
3214
|
+
init_tsdoc_parser();
|
|
3215
|
+
RESOLVING_TYPE_PLACEHOLDER = {
|
|
3216
|
+
kind: "object",
|
|
3217
|
+
properties: [],
|
|
3218
|
+
additionalProperties: true
|
|
3219
|
+
};
|
|
3220
|
+
MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
3221
|
+
}
|
|
3222
|
+
});
|
|
3223
|
+
|
|
3224
|
+
// src/analyzer/program.ts
|
|
3225
|
+
import * as ts4 from "typescript";
|
|
3226
|
+
import * as path from "path";
|
|
3227
|
+
function createProgramContext(filePath) {
|
|
3228
|
+
const absolutePath = path.resolve(filePath);
|
|
3229
|
+
const fileDir = path.dirname(absolutePath);
|
|
3230
|
+
const configPath = ts4.findConfigFile(fileDir, ts4.sys.fileExists.bind(ts4.sys), "tsconfig.json");
|
|
3231
|
+
let compilerOptions;
|
|
3232
|
+
let fileNames;
|
|
3233
|
+
if (configPath) {
|
|
3234
|
+
const configFile = ts4.readConfigFile(configPath, ts4.sys.readFile.bind(ts4.sys));
|
|
3235
|
+
if (configFile.error) {
|
|
3236
|
+
throw new Error(
|
|
3237
|
+
`Error reading tsconfig.json: ${ts4.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
|
|
3238
|
+
);
|
|
3239
|
+
}
|
|
3240
|
+
const parsed = ts4.parseJsonConfigFileContent(
|
|
3241
|
+
configFile.config,
|
|
3242
|
+
ts4.sys,
|
|
3243
|
+
path.dirname(configPath)
|
|
3244
|
+
);
|
|
3245
|
+
if (parsed.errors.length > 0) {
|
|
3246
|
+
const errorMessages = parsed.errors.map((e) => ts4.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
|
|
3247
|
+
throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
|
|
3248
|
+
}
|
|
3249
|
+
compilerOptions = parsed.options;
|
|
3250
|
+
fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
|
|
3251
|
+
} else {
|
|
3252
|
+
compilerOptions = {
|
|
3253
|
+
target: ts4.ScriptTarget.ES2022,
|
|
3254
|
+
module: ts4.ModuleKind.NodeNext,
|
|
3255
|
+
moduleResolution: ts4.ModuleResolutionKind.NodeNext,
|
|
3256
|
+
strict: true,
|
|
3257
|
+
skipLibCheck: true,
|
|
3258
|
+
declaration: true
|
|
3259
|
+
};
|
|
3260
|
+
fileNames = [absolutePath];
|
|
3261
|
+
}
|
|
3262
|
+
const program = ts4.createProgram(fileNames, compilerOptions);
|
|
3263
|
+
const sourceFile = program.getSourceFile(absolutePath);
|
|
3264
|
+
if (!sourceFile) {
|
|
3265
|
+
throw new Error(`Could not find source file: ${absolutePath}`);
|
|
3266
|
+
}
|
|
3267
|
+
return {
|
|
3268
|
+
program,
|
|
3269
|
+
checker: program.getTypeChecker(),
|
|
3270
|
+
sourceFile
|
|
3271
|
+
};
|
|
3272
|
+
}
|
|
3273
|
+
function findNodeByName(sourceFile, name, predicate, getName) {
|
|
3274
|
+
let result = null;
|
|
3275
|
+
function visit(node) {
|
|
3276
|
+
if (result) return;
|
|
3277
|
+
if (predicate(node) && getName(node) === name) {
|
|
3278
|
+
result = node;
|
|
3279
|
+
return;
|
|
3280
|
+
}
|
|
3281
|
+
ts4.forEachChild(node, visit);
|
|
3282
|
+
}
|
|
3283
|
+
visit(sourceFile);
|
|
3284
|
+
return result;
|
|
3285
|
+
}
|
|
3286
|
+
function findClassByName(sourceFile, className) {
|
|
3287
|
+
return findNodeByName(sourceFile, className, ts4.isClassDeclaration, (n) => n.name?.text);
|
|
3288
|
+
}
|
|
3289
|
+
function findInterfaceByName(sourceFile, interfaceName) {
|
|
3290
|
+
return findNodeByName(sourceFile, interfaceName, ts4.isInterfaceDeclaration, (n) => n.name.text);
|
|
3291
|
+
}
|
|
3292
|
+
function findTypeAliasByName(sourceFile, aliasName) {
|
|
3293
|
+
return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
|
|
3294
|
+
}
|
|
3295
|
+
function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry) {
|
|
3296
|
+
const ctx = createProgramContext(filePath);
|
|
3297
|
+
const classDecl = findClassByName(ctx.sourceFile, typeName);
|
|
3298
|
+
if (classDecl !== null) {
|
|
3299
|
+
return analyzeClassToIR(classDecl, ctx.checker, filePath, extensionRegistry);
|
|
3300
|
+
}
|
|
3301
|
+
const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
|
|
3302
|
+
if (interfaceDecl !== null) {
|
|
3303
|
+
return analyzeInterfaceToIR(interfaceDecl, ctx.checker, filePath, extensionRegistry);
|
|
3304
|
+
}
|
|
3305
|
+
const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
|
|
3306
|
+
if (typeAlias !== null) {
|
|
3307
|
+
const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, filePath, extensionRegistry);
|
|
3308
|
+
if (result.ok) {
|
|
3309
|
+
return result.analysis;
|
|
3310
|
+
}
|
|
3311
|
+
throw new Error(result.error);
|
|
3312
|
+
}
|
|
3313
|
+
throw new Error(
|
|
3314
|
+
`Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
|
|
3315
|
+
);
|
|
3316
|
+
}
|
|
3317
|
+
var init_program = __esm({
|
|
3318
|
+
"src/analyzer/program.ts"() {
|
|
3319
|
+
"use strict";
|
|
3320
|
+
init_class_analyzer();
|
|
3321
|
+
}
|
|
3322
|
+
});
|
|
3323
|
+
|
|
3324
|
+
// src/validate/constraint-validator.ts
|
|
3325
|
+
import {
|
|
3326
|
+
analyzeConstraintTargets
|
|
3327
|
+
} from "@formspec/analysis";
|
|
3328
|
+
function validateFieldNode(ctx, field) {
|
|
3329
|
+
const analysis = analyzeConstraintTargets(
|
|
3330
|
+
field.name,
|
|
3331
|
+
field.type,
|
|
3332
|
+
field.constraints,
|
|
3333
|
+
ctx.typeRegistry,
|
|
3334
|
+
ctx.extensionRegistry === void 0 ? void 0 : {
|
|
3335
|
+
extensionRegistry: ctx.extensionRegistry
|
|
3336
|
+
}
|
|
3337
|
+
);
|
|
3338
|
+
ctx.diagnostics.push(...analysis.diagnostics);
|
|
3339
|
+
if (field.type.kind === "object") {
|
|
3340
|
+
for (const property of field.type.properties) {
|
|
3341
|
+
validateObjectProperty(ctx, field.name, property);
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
function validateObjectProperty(ctx, parentName, property) {
|
|
3346
|
+
const qualifiedName = `${parentName}.${property.name}`;
|
|
3347
|
+
const analysis = analyzeConstraintTargets(
|
|
3348
|
+
qualifiedName,
|
|
3349
|
+
property.type,
|
|
3350
|
+
property.constraints,
|
|
3351
|
+
ctx.typeRegistry,
|
|
3352
|
+
ctx.extensionRegistry === void 0 ? void 0 : {
|
|
3353
|
+
extensionRegistry: ctx.extensionRegistry
|
|
3354
|
+
}
|
|
3355
|
+
);
|
|
3356
|
+
ctx.diagnostics.push(...analysis.diagnostics);
|
|
3357
|
+
if (property.type.kind === "object") {
|
|
3358
|
+
for (const nestedProperty of property.type.properties) {
|
|
3359
|
+
validateObjectProperty(ctx, qualifiedName, nestedProperty);
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
function validateElement(ctx, element) {
|
|
3364
|
+
switch (element.kind) {
|
|
3365
|
+
case "field":
|
|
3366
|
+
validateFieldNode(ctx, element);
|
|
3367
|
+
break;
|
|
3368
|
+
case "group":
|
|
3369
|
+
for (const child of element.elements) {
|
|
3370
|
+
validateElement(ctx, child);
|
|
3371
|
+
}
|
|
3372
|
+
break;
|
|
3373
|
+
case "conditional":
|
|
3374
|
+
for (const child of element.elements) {
|
|
3375
|
+
validateElement(ctx, child);
|
|
3376
|
+
}
|
|
3377
|
+
break;
|
|
3378
|
+
default: {
|
|
3379
|
+
const exhaustive = element;
|
|
3380
|
+
throw new Error(`Unhandled element kind: ${String(exhaustive)}`);
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
function validateIR(ir, options) {
|
|
3385
|
+
const ctx = {
|
|
3386
|
+
diagnostics: [],
|
|
3387
|
+
extensionRegistry: options?.extensionRegistry,
|
|
3388
|
+
typeRegistry: ir.typeRegistry
|
|
3389
|
+
};
|
|
3390
|
+
for (const element of ir.elements) {
|
|
3391
|
+
validateElement(ctx, element);
|
|
3392
|
+
}
|
|
3393
|
+
return {
|
|
3394
|
+
diagnostics: ctx.diagnostics,
|
|
3395
|
+
valid: ctx.diagnostics.every((diagnostic) => diagnostic.severity !== "error")
|
|
3396
|
+
};
|
|
3397
|
+
}
|
|
3398
|
+
var init_constraint_validator = __esm({
|
|
3399
|
+
"src/validate/constraint-validator.ts"() {
|
|
3400
|
+
"use strict";
|
|
3401
|
+
}
|
|
3402
|
+
});
|
|
3403
|
+
|
|
3404
|
+
// src/validate/index.ts
|
|
3405
|
+
var init_validate = __esm({
|
|
3406
|
+
"src/validate/index.ts"() {
|
|
3407
|
+
"use strict";
|
|
3408
|
+
init_constraint_validator();
|
|
3409
|
+
}
|
|
3410
|
+
});
|
|
3411
|
+
|
|
3412
|
+
// src/generators/class-schema.ts
|
|
3413
|
+
function generateClassSchemas(analysis, source, options) {
|
|
3414
|
+
const errorDiagnostics = analysis.diagnostics?.filter(
|
|
3415
|
+
(diagnostic) => diagnostic.severity === "error"
|
|
3416
|
+
);
|
|
3417
|
+
if (errorDiagnostics !== void 0 && errorDiagnostics.length > 0) {
|
|
3418
|
+
throw new Error(formatValidationError(errorDiagnostics));
|
|
3419
|
+
}
|
|
3420
|
+
const ir = canonicalizeTSDoc(analysis, source);
|
|
3421
|
+
const validationResult = validateIR(ir, {
|
|
3422
|
+
...options?.extensionRegistry !== void 0 && {
|
|
3423
|
+
extensionRegistry: options.extensionRegistry
|
|
3424
|
+
},
|
|
3425
|
+
...options?.vendorPrefix !== void 0 && { vendorPrefix: options.vendorPrefix }
|
|
3426
|
+
});
|
|
3427
|
+
if (!validationResult.valid) {
|
|
3428
|
+
throw new Error(formatValidationError(validationResult.diagnostics));
|
|
3429
|
+
}
|
|
3430
|
+
return {
|
|
3431
|
+
jsonSchema: generateJsonSchemaFromIR(ir, options),
|
|
3432
|
+
uiSchema: generateUiSchemaFromIR(ir)
|
|
3433
|
+
};
|
|
3434
|
+
}
|
|
3435
|
+
function formatValidationError(diagnostics) {
|
|
3436
|
+
const lines = diagnostics.map((diagnostic) => {
|
|
3437
|
+
const primary = formatLocation(diagnostic.primaryLocation);
|
|
3438
|
+
const related = diagnostic.relatedLocations.length > 0 ? ` [related: ${diagnostic.relatedLocations.map(formatLocation).join(", ")}]` : "";
|
|
3439
|
+
return `${diagnostic.code}: ${diagnostic.message} (${primary})${related}`;
|
|
3440
|
+
});
|
|
3441
|
+
return `FormSpec validation failed:
|
|
3442
|
+
${lines.map((line) => `- ${line}`).join("\n")}`;
|
|
3443
|
+
}
|
|
3444
|
+
function formatLocation(location) {
|
|
3445
|
+
return `${location.file}:${String(location.line)}:${String(location.column)}`;
|
|
3446
|
+
}
|
|
3447
|
+
function generateSchemasFromClass(options) {
|
|
3448
|
+
const ctx = createProgramContext(options.filePath);
|
|
3449
|
+
const classDecl = findClassByName(ctx.sourceFile, options.className);
|
|
3450
|
+
if (!classDecl) {
|
|
3451
|
+
throw new Error(`Class "${options.className}" not found in ${options.filePath}`);
|
|
3452
|
+
}
|
|
3453
|
+
const analysis = analyzeClassToIR(
|
|
3454
|
+
classDecl,
|
|
3455
|
+
ctx.checker,
|
|
3456
|
+
options.filePath,
|
|
3457
|
+
options.extensionRegistry
|
|
3458
|
+
);
|
|
3459
|
+
return generateClassSchemas(
|
|
3460
|
+
analysis,
|
|
3461
|
+
{ file: options.filePath },
|
|
3462
|
+
{
|
|
3463
|
+
extensionRegistry: options.extensionRegistry,
|
|
3464
|
+
vendorPrefix: options.vendorPrefix
|
|
3465
|
+
}
|
|
3466
|
+
);
|
|
3467
|
+
}
|
|
3468
|
+
function generateSchemas(options) {
|
|
3469
|
+
const analysis = analyzeNamedTypeToIR(
|
|
3470
|
+
options.filePath,
|
|
3471
|
+
options.typeName,
|
|
3472
|
+
options.extensionRegistry
|
|
3473
|
+
);
|
|
3474
|
+
return generateClassSchemas(analysis, { file: options.filePath }, options);
|
|
3475
|
+
}
|
|
3476
|
+
var init_class_schema = __esm({
|
|
3477
|
+
"src/generators/class-schema.ts"() {
|
|
3478
|
+
"use strict";
|
|
3479
|
+
init_program();
|
|
3480
|
+
init_class_analyzer();
|
|
3481
|
+
init_canonicalize();
|
|
3482
|
+
init_ir_generator();
|
|
3483
|
+
init_ir_generator2();
|
|
3484
|
+
init_validate();
|
|
3485
|
+
}
|
|
3486
|
+
});
|
|
3487
|
+
|
|
3488
|
+
// src/generators/mixed-authoring.ts
|
|
3489
|
+
function buildMixedAuthoringSchemas(options) {
|
|
3490
|
+
const { filePath, typeName, overlays, ...schemaOptions } = options;
|
|
3491
|
+
const analysis = analyzeNamedTypeToIR(filePath, typeName, schemaOptions.extensionRegistry);
|
|
3492
|
+
const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
|
|
3493
|
+
const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
|
|
3494
|
+
return {
|
|
3495
|
+
jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
|
|
3496
|
+
uiSchema: generateUiSchemaFromIR(ir)
|
|
3497
|
+
};
|
|
3498
|
+
}
|
|
3499
|
+
function composeAnalysisWithOverlays(analysis, overlays) {
|
|
3500
|
+
const overlayIR = canonicalizeChainDSL(overlays);
|
|
3501
|
+
const overlayFields = collectOverlayFields(overlayIR.elements);
|
|
3502
|
+
if (overlayFields.length === 0) {
|
|
3503
|
+
return analysis;
|
|
3504
|
+
}
|
|
3505
|
+
const overlayByName = /* @__PURE__ */ new Map();
|
|
3506
|
+
for (const field of overlayFields) {
|
|
3507
|
+
if (overlayByName.has(field.name)) {
|
|
3508
|
+
throw new Error(`Mixed-authoring overlays define "${field.name}" more than once`);
|
|
3509
|
+
}
|
|
3510
|
+
overlayByName.set(field.name, field);
|
|
3511
|
+
}
|
|
3512
|
+
const mergedFields = [];
|
|
3513
|
+
for (const baseField of analysis.fields) {
|
|
3514
|
+
const overlayField = overlayByName.get(baseField.name);
|
|
3515
|
+
if (overlayField === void 0) {
|
|
3516
|
+
mergedFields.push(baseField);
|
|
3517
|
+
continue;
|
|
3518
|
+
}
|
|
3519
|
+
mergedFields.push(mergeFieldOverlay(baseField, overlayField, analysis.typeRegistry));
|
|
3520
|
+
overlayByName.delete(baseField.name);
|
|
3521
|
+
}
|
|
3522
|
+
if (overlayByName.size > 0) {
|
|
3523
|
+
const unknownFields = [...overlayByName.keys()].sort().join(", ");
|
|
3524
|
+
throw new Error(
|
|
3525
|
+
`Mixed-authoring overlays reference fields that are not present in the static model: ${unknownFields}`
|
|
3526
|
+
);
|
|
3527
|
+
}
|
|
3528
|
+
return {
|
|
3529
|
+
...analysis,
|
|
3530
|
+
fields: mergedFields
|
|
3531
|
+
};
|
|
3532
|
+
}
|
|
3533
|
+
function collectOverlayFields(elements) {
|
|
3534
|
+
const fields = [];
|
|
3535
|
+
for (const element of elements) {
|
|
3536
|
+
switch (element.kind) {
|
|
3537
|
+
case "field":
|
|
3538
|
+
fields.push(element);
|
|
3539
|
+
break;
|
|
3540
|
+
case "group":
|
|
3541
|
+
fields.push(...collectOverlayFields(element.elements));
|
|
3542
|
+
break;
|
|
3543
|
+
case "conditional":
|
|
3544
|
+
fields.push(...collectOverlayFields(element.elements));
|
|
3545
|
+
break;
|
|
3546
|
+
default: {
|
|
3547
|
+
const _exhaustive = element;
|
|
3548
|
+
void _exhaustive;
|
|
3549
|
+
}
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
return fields;
|
|
3553
|
+
}
|
|
3554
|
+
function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
|
|
3555
|
+
assertSupportedOverlayField(baseField, overlayField);
|
|
3556
|
+
return {
|
|
3557
|
+
...baseField,
|
|
3558
|
+
type: mergeFieldType(baseField, overlayField, typeRegistry),
|
|
3559
|
+
annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
|
|
3560
|
+
};
|
|
3561
|
+
}
|
|
3562
|
+
function assertSupportedOverlayField(baseField, overlayField) {
|
|
3563
|
+
if (overlayField.constraints.length > 0) {
|
|
3564
|
+
throw new Error(
|
|
3565
|
+
`Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
|
|
3566
|
+
);
|
|
3567
|
+
}
|
|
3568
|
+
if (overlayField.required && !baseField.required) {
|
|
3569
|
+
throw new Error(
|
|
3570
|
+
`Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
|
|
3571
|
+
);
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
function mergeFieldType(baseField, overlayField, typeRegistry) {
|
|
3575
|
+
const { type: baseType } = baseField;
|
|
3576
|
+
const { type: overlayType } = overlayField;
|
|
3577
|
+
if (overlayType.kind === "object" || overlayType.kind === "array") {
|
|
3578
|
+
throw new Error(
|
|
3579
|
+
`Mixed-authoring overlays do not support nested object or array overlays for "${baseField.name}"`
|
|
3580
|
+
);
|
|
3581
|
+
}
|
|
3582
|
+
if (overlayType.kind === "dynamic") {
|
|
3583
|
+
if (!isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry)) {
|
|
3584
|
+
throw new Error(
|
|
3585
|
+
`Mixed-authoring overlay for "${baseField.name}" is incompatible with the static field type`
|
|
3586
|
+
);
|
|
3587
|
+
}
|
|
3588
|
+
return overlayType;
|
|
3589
|
+
}
|
|
3590
|
+
if (!isSameStaticTypeShape(baseType, overlayType)) {
|
|
3591
|
+
throw new Error(
|
|
3592
|
+
`Mixed-authoring overlay for "${baseField.name}" must preserve the static field type`
|
|
3593
|
+
);
|
|
3594
|
+
}
|
|
3595
|
+
return baseType;
|
|
3596
|
+
}
|
|
3597
|
+
function isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry) {
|
|
3598
|
+
const overlayType = overlayField.type;
|
|
3599
|
+
if (overlayType.kind !== "dynamic") {
|
|
3600
|
+
return false;
|
|
3601
|
+
}
|
|
3602
|
+
const resolvedBaseType = resolveReferenceType(baseField.type, typeRegistry);
|
|
3603
|
+
if (resolvedBaseType === null) {
|
|
3604
|
+
return false;
|
|
3605
|
+
}
|
|
3606
|
+
if (overlayType.dynamicKind === "enum") {
|
|
3607
|
+
return resolvedBaseType.kind === "primitive" ? resolvedBaseType.primitiveKind === "string" : resolvedBaseType.kind === "enum";
|
|
3608
|
+
}
|
|
3609
|
+
return resolvedBaseType.kind === "object" || resolvedBaseType.kind === "record";
|
|
3610
|
+
}
|
|
3611
|
+
function resolveReferenceType(type, typeRegistry, seen = /* @__PURE__ */ new Set()) {
|
|
3612
|
+
if (type.kind !== "reference") {
|
|
3613
|
+
return type;
|
|
3614
|
+
}
|
|
3615
|
+
if (seen.has(type.name)) {
|
|
3616
|
+
return null;
|
|
3617
|
+
}
|
|
3618
|
+
const definition = typeRegistry[type.name];
|
|
3619
|
+
if (definition === void 0) {
|
|
3620
|
+
return null;
|
|
3621
|
+
}
|
|
3622
|
+
seen.add(type.name);
|
|
3623
|
+
return resolveReferenceType(definition.type, typeRegistry, seen);
|
|
3624
|
+
}
|
|
3625
|
+
function isSameStaticTypeShape(baseType, overlayType) {
|
|
3626
|
+
if (baseType.kind !== overlayType.kind) {
|
|
3627
|
+
return false;
|
|
3628
|
+
}
|
|
3629
|
+
switch (baseType.kind) {
|
|
3630
|
+
case "primitive":
|
|
3631
|
+
return overlayType.kind === "primitive" && baseType.primitiveKind === overlayType.primitiveKind;
|
|
3632
|
+
case "enum":
|
|
3633
|
+
return overlayType.kind === "enum";
|
|
3634
|
+
case "dynamic":
|
|
3635
|
+
return overlayType.kind === "dynamic" && baseType.dynamicKind === overlayType.dynamicKind && baseType.sourceKey === overlayType.sourceKey;
|
|
3636
|
+
case "record":
|
|
3637
|
+
return overlayType.kind === "record";
|
|
3638
|
+
case "reference":
|
|
3639
|
+
return overlayType.kind === "reference" && baseType.name === overlayType.name;
|
|
3640
|
+
case "union":
|
|
3641
|
+
return overlayType.kind === "union";
|
|
3642
|
+
case "custom":
|
|
3643
|
+
return overlayType.kind === "custom" && baseType.typeId === overlayType.typeId;
|
|
3644
|
+
case "object":
|
|
3645
|
+
case "array":
|
|
3646
|
+
return true;
|
|
3647
|
+
default: {
|
|
3648
|
+
const _exhaustive = baseType;
|
|
3649
|
+
return _exhaustive;
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
function mergeAnnotations(baseAnnotations, overlayAnnotations) {
|
|
3654
|
+
const baseKeys = new Set(baseAnnotations.map(annotationKey));
|
|
3655
|
+
const overlayOnly = overlayAnnotations.filter(
|
|
3656
|
+
(annotation) => !baseKeys.has(annotationKey(annotation))
|
|
3657
|
+
);
|
|
3658
|
+
return [...baseAnnotations, ...overlayOnly];
|
|
3659
|
+
}
|
|
3660
|
+
function annotationKey(annotation) {
|
|
3661
|
+
return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;
|
|
3662
|
+
}
|
|
3663
|
+
var init_mixed_authoring = __esm({
|
|
3664
|
+
"src/generators/mixed-authoring.ts"() {
|
|
3665
|
+
"use strict";
|
|
3666
|
+
init_ir_generator();
|
|
3667
|
+
init_ir_generator2();
|
|
3668
|
+
init_canonicalize();
|
|
3669
|
+
init_program();
|
|
3670
|
+
}
|
|
3671
|
+
});
|
|
3672
|
+
|
|
3673
|
+
// src/index.ts
|
|
3674
|
+
var index_exports = {};
|
|
3675
|
+
__export(index_exports, {
|
|
3676
|
+
buildFormSchemas: () => buildFormSchemas,
|
|
3677
|
+
buildMixedAuthoringSchemas: () => buildMixedAuthoringSchemas,
|
|
3678
|
+
categorizationSchema: () => categorizationSchema,
|
|
3679
|
+
categorySchema: () => categorySchema,
|
|
3680
|
+
controlSchema: () => controlSchema,
|
|
3681
|
+
createExtensionRegistry: () => createExtensionRegistry,
|
|
3682
|
+
generateJsonSchema: () => generateJsonSchema,
|
|
3683
|
+
generateJsonSchemaFromIR: () => generateJsonSchemaFromIR,
|
|
3684
|
+
generateSchemas: () => generateSchemas,
|
|
3685
|
+
generateSchemasFromClass: () => generateSchemasFromClass,
|
|
3686
|
+
generateUiSchema: () => generateUiSchema,
|
|
3687
|
+
getSchemaExtension: () => getSchemaExtension,
|
|
3688
|
+
groupLayoutSchema: () => groupLayoutSchema,
|
|
3689
|
+
horizontalLayoutSchema: () => horizontalLayoutSchema,
|
|
3690
|
+
jsonSchema7Schema: () => jsonSchema7Schema,
|
|
3691
|
+
jsonSchemaTypeSchema: () => jsonSchemaTypeSchema,
|
|
3692
|
+
labelElementSchema: () => labelElementSchema,
|
|
3693
|
+
ruleConditionSchema: () => ruleConditionSchema,
|
|
3694
|
+
ruleEffectSchema: () => ruleEffectSchema,
|
|
3695
|
+
ruleSchema: () => ruleSchema,
|
|
3696
|
+
schemaBasedConditionSchema: () => schemaBasedConditionSchema,
|
|
3697
|
+
setSchemaExtension: () => setSchemaExtension,
|
|
3698
|
+
uiSchemaElementSchema: () => uiSchemaElementSchema,
|
|
3699
|
+
uiSchemaElementTypeSchema: () => uiSchemaElementTypeSchema,
|
|
3700
|
+
uiSchemaSchema: () => uiSchema,
|
|
3701
|
+
verticalLayoutSchema: () => verticalLayoutSchema,
|
|
3702
|
+
writeSchemas: () => writeSchemas
|
|
3703
|
+
});
|
|
3704
|
+
import * as fs from "fs";
|
|
3705
|
+
import * as path2 from "path";
|
|
3706
|
+
function buildFormSchemas(form, options) {
|
|
3707
|
+
return {
|
|
3708
|
+
jsonSchema: generateJsonSchema(form, options),
|
|
3709
|
+
uiSchema: generateUiSchema(form)
|
|
3710
|
+
};
|
|
3711
|
+
}
|
|
3712
|
+
function writeSchemas(form, options) {
|
|
3713
|
+
const { outDir, name = "schema", indent = 2, extensionRegistry, vendorPrefix } = options;
|
|
3714
|
+
const buildOptions = extensionRegistry === void 0 && vendorPrefix === void 0 ? void 0 : {
|
|
3715
|
+
extensionRegistry,
|
|
3716
|
+
vendorPrefix
|
|
3717
|
+
};
|
|
3718
|
+
const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form, buildOptions);
|
|
3719
|
+
if (!fs.existsSync(outDir)) {
|
|
3720
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
3721
|
+
}
|
|
3722
|
+
const jsonSchemaPath = path2.join(outDir, `${name}-schema.json`);
|
|
3723
|
+
const uiSchemaPath = path2.join(outDir, `${name}-uischema.json`);
|
|
3724
|
+
fs.writeFileSync(jsonSchemaPath, JSON.stringify(jsonSchema, null, indent));
|
|
3725
|
+
fs.writeFileSync(uiSchemaPath, JSON.stringify(uiSchema2, null, indent));
|
|
3726
|
+
return { jsonSchemaPath, uiSchemaPath };
|
|
3727
|
+
}
|
|
3728
|
+
var init_index = __esm({
|
|
3729
|
+
"src/index.ts"() {
|
|
3730
|
+
"use strict";
|
|
3731
|
+
init_generator();
|
|
3732
|
+
init_generator2();
|
|
3733
|
+
init_ir_generator();
|
|
3734
|
+
init_types();
|
|
3735
|
+
init_extensions();
|
|
3736
|
+
init_schema();
|
|
3737
|
+
init_schema2();
|
|
3738
|
+
init_generator();
|
|
3739
|
+
init_ir_generator();
|
|
3740
|
+
init_generator2();
|
|
3741
|
+
init_class_schema();
|
|
3742
|
+
init_mixed_authoring();
|
|
3743
|
+
}
|
|
3744
|
+
});
|
|
3745
|
+
|
|
3746
|
+
// src/cli.ts
|
|
3747
|
+
import * as path3 from "path";
|
|
3748
|
+
import { pathToFileURL } from "url";
|
|
18
3749
|
function printHelp() {
|
|
19
|
-
|
|
3750
|
+
console.log(`
|
|
20
3751
|
FormSpec Build CLI - Generate JSON Schema and UI Schema
|
|
21
3752
|
|
|
22
3753
|
Usage:
|
|
@@ -38,101 +3769,93 @@ The input file should export a FormSpec as its default export or as 'form':
|
|
|
38
3769
|
`);
|
|
39
3770
|
}
|
|
40
3771
|
function parseArgs(args) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
process.exit(0);
|
|
51
|
-
}
|
|
52
|
-
if (arg === "-o" || arg === "--out-dir") {
|
|
53
|
-
const nextArg = args[i + 1];
|
|
54
|
-
if (!nextArg) {
|
|
55
|
-
console.error("Error: --out-dir requires a value");
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
outDir = nextArg;
|
|
59
|
-
i++;
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
if (arg === "-n" || arg === "--name") {
|
|
63
|
-
const nextArg = args[i + 1];
|
|
64
|
-
if (!nextArg) {
|
|
65
|
-
console.error("Error: --name requires a value");
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
name = nextArg;
|
|
69
|
-
i++;
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
if (arg.startsWith("-")) {
|
|
73
|
-
console.error(`Error: Unknown option: ${arg}`);
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
positional.push(arg);
|
|
3772
|
+
const positional = [];
|
|
3773
|
+
let outDir = "./generated";
|
|
3774
|
+
let name = "";
|
|
3775
|
+
for (let i = 0; i < args.length; i++) {
|
|
3776
|
+
const arg = args[i];
|
|
3777
|
+
if (arg === void 0) continue;
|
|
3778
|
+
if (arg === "-h" || arg === "--help") {
|
|
3779
|
+
printHelp();
|
|
3780
|
+
process.exit(0);
|
|
77
3781
|
}
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
|
|
3782
|
+
if (arg === "-o" || arg === "--out-dir") {
|
|
3783
|
+
const nextArg = args[i + 1];
|
|
3784
|
+
if (!nextArg) {
|
|
3785
|
+
console.error("Error: --out-dir requires a value");
|
|
81
3786
|
return null;
|
|
3787
|
+
}
|
|
3788
|
+
outDir = nextArg;
|
|
3789
|
+
i++;
|
|
3790
|
+
continue;
|
|
82
3791
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
3792
|
+
if (arg === "-n" || arg === "--name") {
|
|
3793
|
+
const nextArg = args[i + 1];
|
|
3794
|
+
if (!nextArg) {
|
|
3795
|
+
console.error("Error: --name requires a value");
|
|
86
3796
|
return null;
|
|
3797
|
+
}
|
|
3798
|
+
name = nextArg;
|
|
3799
|
+
i++;
|
|
3800
|
+
continue;
|
|
87
3801
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
3802
|
+
if (arg.startsWith("-")) {
|
|
3803
|
+
console.error(`Error: Unknown option: ${arg}`);
|
|
3804
|
+
return null;
|
|
91
3805
|
}
|
|
92
|
-
|
|
3806
|
+
positional.push(arg);
|
|
3807
|
+
}
|
|
3808
|
+
if (positional.length === 0) {
|
|
3809
|
+
console.error("Error: No input file specified");
|
|
3810
|
+
printHelp();
|
|
3811
|
+
return null;
|
|
3812
|
+
}
|
|
3813
|
+
const inputFile = positional[0];
|
|
3814
|
+
if (!inputFile) {
|
|
3815
|
+
console.error("Error: No input file specified");
|
|
3816
|
+
return null;
|
|
3817
|
+
}
|
|
3818
|
+
if (!name) {
|
|
3819
|
+
name = path3.basename(inputFile, path3.extname(inputFile));
|
|
3820
|
+
}
|
|
3821
|
+
return { inputFile, outDir, name };
|
|
93
3822
|
}
|
|
94
3823
|
async function main() {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
catch (error) {
|
|
128
|
-
if (error instanceof Error) {
|
|
129
|
-
console.error(`Error: ${error.message}`);
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
console.error("Error:", error);
|
|
133
|
-
}
|
|
134
|
-
process.exit(1);
|
|
3824
|
+
const args = process.argv.slice(2);
|
|
3825
|
+
const options = parseArgs(args);
|
|
3826
|
+
if (!options) {
|
|
3827
|
+
process.exit(1);
|
|
3828
|
+
}
|
|
3829
|
+
const { inputFile, outDir, name } = options;
|
|
3830
|
+
const absoluteInput = path3.resolve(process.cwd(), inputFile);
|
|
3831
|
+
try {
|
|
3832
|
+
const fileUrl = pathToFileURL(absoluteInput).href;
|
|
3833
|
+
const module = await import(fileUrl);
|
|
3834
|
+
const form = module["default"] ?? module["form"];
|
|
3835
|
+
if (!form || typeof form !== "object" || !("elements" in form)) {
|
|
3836
|
+
console.error("Error: Input file must export a FormSpec as default export or as 'form'");
|
|
3837
|
+
console.error("Example:");
|
|
3838
|
+
console.error(' export default formspec(field.text("name"));');
|
|
3839
|
+
console.error(" // or");
|
|
3840
|
+
console.error(' export const form = formspec(field.text("name"));');
|
|
3841
|
+
process.exit(1);
|
|
3842
|
+
}
|
|
3843
|
+
const { writeSchemas: writeSchemas2 } = await Promise.resolve().then(() => (init_index(), index_exports));
|
|
3844
|
+
const { jsonSchemaPath, uiSchemaPath } = writeSchemas2(
|
|
3845
|
+
form,
|
|
3846
|
+
{ outDir, name }
|
|
3847
|
+
);
|
|
3848
|
+
console.log("Generated:");
|
|
3849
|
+
console.log(` ${jsonSchemaPath}`);
|
|
3850
|
+
console.log(` ${uiSchemaPath}`);
|
|
3851
|
+
} catch (error) {
|
|
3852
|
+
if (error instanceof Error) {
|
|
3853
|
+
console.error(`Error: ${error.message}`);
|
|
3854
|
+
} else {
|
|
3855
|
+
console.error("Error:", error);
|
|
135
3856
|
}
|
|
3857
|
+
process.exit(1);
|
|
3858
|
+
}
|
|
136
3859
|
}
|
|
137
3860
|
void main();
|
|
138
3861
|
//# sourceMappingURL=cli.js.map
|