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