@no-mess/client 0.4.0 → 0.5.1
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 +49 -2
- package/dist/react/no-mess-provider.d.ts.map +1 -1
- package/dist/react/no-mess-provider.js +12 -3
- package/dist/react/no-mess-provider.js.map +1 -1
- package/dist/schema/define-content-type.d.ts +14 -4
- package/dist/schema/define-content-type.d.ts.map +1 -1
- package/dist/schema/define-content-type.js +87 -20
- package/dist/schema/define-content-type.js.map +1 -1
- package/dist/schema/define-schema.d.ts +6 -3
- package/dist/schema/define-schema.d.ts.map +1 -1
- package/dist/schema/define-schema.js +20 -2
- package/dist/schema/define-schema.js.map +1 -1
- package/dist/schema/field-builders.d.ts +47 -17
- package/dist/schema/field-builders.d.ts.map +1 -1
- package/dist/schema/field-builders.js +26 -0
- package/dist/schema/field-builders.js.map +1 -1
- package/dist/schema/index.d.ts +6 -5
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +3 -2
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/parse-schema.d.ts +1 -2
- package/dist/schema/parse-schema.d.ts.map +1 -1
- package/dist/schema/parse-schema.js +503 -275
- package/dist/schema/parse-schema.js.map +1 -1
- package/dist/schema/schema-types.d.ts +53 -14
- package/dist/schema/schema-types.d.ts.map +1 -1
- package/dist/schema/schema-types.js +10 -3
- package/dist/schema/schema-types.js.map +1 -1
- package/dist/schema/serialize-schema.d.ts.map +1 -1
- package/dist/schema/serialize-schema.js +125 -22
- package/dist/schema/serialize-schema.js.map +1 -1
- package/dist/schema/tree-utils.d.ts +18 -0
- package/dist/schema/tree-utils.d.ts.map +1 -0
- package/dist/schema/tree-utils.js +203 -0
- package/dist/schema/tree-utils.js.map +1 -0
- package/dist/types.d.ts +4 -12
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import { FIELD_TYPES, } from "./schema-types";
|
|
2
2
|
const FIELD_TYPE_SET = new Set(FIELD_TYPES);
|
|
3
|
+
const DEFINITION_CALL_PATTERN = /define(ContentType|Template|Fragment)\s*\(/g;
|
|
4
|
+
const VARIABLE_DEFINITION_PATTERN = /(?:const|let|var|export\s+const)\s+(\w+)\s*=\s*define(ContentType|Template|Fragment)\s*\(/g;
|
|
3
5
|
/**
|
|
4
|
-
* Parses schema DSL source text and extracts
|
|
5
|
-
* Custom recursive-descent parser — no TS compiler, no eval.
|
|
6
|
+
* Parses schema DSL source text and extracts template/fragment definitions.
|
|
6
7
|
* Never throws; returns partial results + errors.
|
|
7
8
|
*/
|
|
8
9
|
export function parseSchemaSource(source) {
|
|
9
10
|
const errors = [];
|
|
10
11
|
const warnings = [];
|
|
11
12
|
const contentTypes = [];
|
|
12
|
-
// Strip comments (line and block) while preserving line positions
|
|
13
13
|
const stripped = stripComments(source);
|
|
14
|
-
|
|
15
|
-
const pattern = /defineContentType\s*\(/g;
|
|
14
|
+
const references = collectDefinitionReferences(stripped);
|
|
16
15
|
let match;
|
|
17
|
-
while ((match =
|
|
16
|
+
while ((match = DEFINITION_CALL_PATTERN.exec(stripped)) !== null) {
|
|
17
|
+
const definitionName = match[1];
|
|
18
18
|
const startIdx = match.index + match[0].length;
|
|
19
19
|
const line = lineAt(source, match.index);
|
|
20
20
|
try {
|
|
21
|
-
const result =
|
|
21
|
+
const result = parseDefinitionArgs(stripped, startIdx, line, definitionName, references);
|
|
22
22
|
if (result.contentType) {
|
|
23
23
|
contentTypes.push(result.contentType);
|
|
24
24
|
}
|
|
@@ -33,12 +33,10 @@ export function parseSchemaSource(source) {
|
|
|
33
33
|
errors.push({
|
|
34
34
|
line,
|
|
35
35
|
column: 0,
|
|
36
|
-
message:
|
|
36
|
+
message: `Failed to parse define${definitionName} call`,
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
// Also support standalone defineSchema({ contentTypes: [...] }) wrapping
|
|
41
|
-
// The individual defineContentType calls inside are already captured above
|
|
42
40
|
return {
|
|
43
41
|
success: errors.length === 0 && contentTypes.length > 0,
|
|
44
42
|
contentTypes,
|
|
@@ -46,282 +44,530 @@ export function parseSchemaSource(source) {
|
|
|
46
44
|
warnings,
|
|
47
45
|
};
|
|
48
46
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const ch = source[i];
|
|
59
|
-
const next = source[i + 1];
|
|
60
|
-
if (escaped) {
|
|
61
|
-
result += ch;
|
|
62
|
-
escaped = false;
|
|
63
|
-
i++;
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
if (inString) {
|
|
67
|
-
if (ch === "\\") {
|
|
68
|
-
escaped = true;
|
|
69
|
-
result += ch;
|
|
70
|
-
i++;
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
if (ch === inString) {
|
|
74
|
-
inString = null;
|
|
75
|
-
}
|
|
76
|
-
result += ch;
|
|
77
|
-
i++;
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
// Template literals
|
|
81
|
-
if (ch === "`") {
|
|
82
|
-
inString = "`";
|
|
83
|
-
result += ch;
|
|
84
|
-
i++;
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
if (ch === '"' || ch === "'") {
|
|
88
|
-
inString = ch;
|
|
89
|
-
result += ch;
|
|
90
|
-
i++;
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
// Line comment
|
|
94
|
-
if (ch === "/" && next === "/") {
|
|
95
|
-
while (i < source.length && source[i] !== "\n") {
|
|
96
|
-
result += " ";
|
|
97
|
-
i++;
|
|
98
|
-
}
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
// Block comment
|
|
102
|
-
if (ch === "/" && next === "*") {
|
|
103
|
-
i += 2;
|
|
104
|
-
while (i < source.length) {
|
|
105
|
-
if (source[i] === "*" && source[i + 1] === "/") {
|
|
106
|
-
i += 2;
|
|
107
|
-
break;
|
|
108
|
-
}
|
|
109
|
-
result += source[i] === "\n" ? "\n" : " ";
|
|
110
|
-
i++;
|
|
111
|
-
}
|
|
47
|
+
function collectDefinitionReferences(source) {
|
|
48
|
+
const refs = new Map();
|
|
49
|
+
let match;
|
|
50
|
+
while ((match = VARIABLE_DEFINITION_PATTERN.exec(source)) !== null) {
|
|
51
|
+
const variableName = match[1];
|
|
52
|
+
const definitionName = match[2];
|
|
53
|
+
const slugStart = skipWhitespace(source, match.index + match[0].length);
|
|
54
|
+
const slug = parseStringLiteral(source, slugStart)?.value;
|
|
55
|
+
if (!slug) {
|
|
112
56
|
continue;
|
|
113
57
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
function lineAt(source, index) {
|
|
120
|
-
let line = 1;
|
|
121
|
-
for (let i = 0; i < index && i < source.length; i++) {
|
|
122
|
-
if (source[i] === "\n")
|
|
123
|
-
line++;
|
|
124
|
-
}
|
|
125
|
-
return line;
|
|
126
|
-
}
|
|
127
|
-
function columnAt(source, index) {
|
|
128
|
-
let col = 1;
|
|
129
|
-
for (let i = index - 1; i >= 0; i--) {
|
|
130
|
-
if (source[i] === "\n")
|
|
131
|
-
break;
|
|
132
|
-
col++;
|
|
58
|
+
refs.set(variableName, {
|
|
59
|
+
kind: definitionName === "Fragment" ? "fragment" : "template",
|
|
60
|
+
slug,
|
|
61
|
+
});
|
|
133
62
|
}
|
|
134
|
-
return
|
|
63
|
+
return refs;
|
|
135
64
|
}
|
|
136
|
-
|
|
137
|
-
* Parse the arguments of defineContentType(slug, config).
|
|
138
|
-
* `startIdx` points to the character after the opening `(`.
|
|
139
|
-
*/
|
|
140
|
-
function parseDefineContentTypeArgs(source, startIdx, baseLine) {
|
|
65
|
+
function parseDefinitionArgs(source, startIdx, baseLine, definitionName, references) {
|
|
141
66
|
const errors = [];
|
|
142
67
|
const warnings = [];
|
|
143
|
-
let
|
|
144
|
-
|
|
145
|
-
const slugResult = parseStringLiteral(source, i);
|
|
68
|
+
let index = skipWhitespace(source, startIdx);
|
|
69
|
+
const slugResult = parseStringLiteral(source, index);
|
|
146
70
|
if (!slugResult) {
|
|
147
71
|
errors.push({
|
|
148
72
|
line: baseLine,
|
|
149
|
-
column: columnAt(source,
|
|
150
|
-
message: "Expected string literal for
|
|
73
|
+
column: columnAt(source, index),
|
|
74
|
+
message: "Expected string literal for schema slug",
|
|
151
75
|
});
|
|
152
76
|
return { contentType: null, errors, warnings };
|
|
153
77
|
}
|
|
154
78
|
const slug = slugResult.value;
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (source[i] !== ",") {
|
|
79
|
+
index = skipWhitespace(source, slugResult.end);
|
|
80
|
+
if (source[index] !== ",") {
|
|
158
81
|
errors.push({
|
|
159
|
-
line: lineAt(source,
|
|
160
|
-
column: columnAt(source,
|
|
82
|
+
line: lineAt(source, index),
|
|
83
|
+
column: columnAt(source, index),
|
|
161
84
|
message: "Expected comma after slug",
|
|
162
85
|
});
|
|
163
86
|
return { contentType: null, errors, warnings };
|
|
164
87
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (source[i] !== "{") {
|
|
88
|
+
index = skipWhitespace(source, index + 1);
|
|
89
|
+
if (source[index] !== "{") {
|
|
168
90
|
errors.push({
|
|
169
|
-
line: lineAt(source,
|
|
170
|
-
column: columnAt(source,
|
|
171
|
-
message: "Expected object literal for
|
|
91
|
+
line: lineAt(source, index),
|
|
92
|
+
column: columnAt(source, index),
|
|
93
|
+
message: "Expected object literal for schema config",
|
|
172
94
|
});
|
|
173
95
|
return { contentType: null, errors, warnings };
|
|
174
96
|
}
|
|
175
|
-
const
|
|
176
|
-
if (!
|
|
97
|
+
const configBlock = extractBalanced(source, index, "{", "}");
|
|
98
|
+
if (!configBlock) {
|
|
177
99
|
errors.push({
|
|
178
|
-
line: lineAt(source,
|
|
179
|
-
column: columnAt(source,
|
|
100
|
+
line: lineAt(source, index),
|
|
101
|
+
column: columnAt(source, index),
|
|
180
102
|
message: "Unterminated config object",
|
|
181
103
|
});
|
|
182
104
|
return { contentType: null, errors, warnings };
|
|
183
105
|
}
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
const name = extractPropertyString(configBody, "name");
|
|
106
|
+
const configEntries = parseObjectEntries(configBlock.inner);
|
|
107
|
+
const name = parseStringValue(findObjectEntry(configEntries, "name")?.value);
|
|
187
108
|
if (!name) {
|
|
188
109
|
warnings.push({
|
|
189
110
|
line: baseLine,
|
|
190
|
-
message: `Missing "name" for
|
|
111
|
+
message: `Missing "name" for schema "${slug}"; using slug as name`,
|
|
191
112
|
});
|
|
192
113
|
}
|
|
193
|
-
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
114
|
+
const description = parseStringValue(findObjectEntry(configEntries, "description")?.value);
|
|
115
|
+
const route = parseStringValue(findObjectEntry(configEntries, "route")?.value);
|
|
116
|
+
const rawMode = parseStringValue(findObjectEntry(configEntries, "mode")?.value);
|
|
117
|
+
const mode = rawMode === "singleton" ? "singleton" : "collection";
|
|
118
|
+
const fieldsEntry = findObjectEntry(configEntries, "fields");
|
|
119
|
+
const fields = fieldsEntry
|
|
120
|
+
? parseFieldEntries(unwrapObjectLiteral(fieldsEntry.value), baseLine, errors, warnings, references)
|
|
121
|
+
: [];
|
|
122
|
+
if (!fieldsEntry) {
|
|
123
|
+
warnings.push({
|
|
124
|
+
line: baseLine,
|
|
125
|
+
message: `Schema "${slug}" is missing a fields object`,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
if (definitionName === "Fragment") {
|
|
129
|
+
return {
|
|
130
|
+
contentType: {
|
|
131
|
+
kind: "fragment",
|
|
132
|
+
slug,
|
|
133
|
+
name: name || slug,
|
|
134
|
+
...(description ? { description } : {}),
|
|
135
|
+
fields,
|
|
136
|
+
},
|
|
137
|
+
errors,
|
|
138
|
+
warnings,
|
|
139
|
+
};
|
|
213
140
|
}
|
|
214
|
-
return {
|
|
141
|
+
return {
|
|
142
|
+
contentType: {
|
|
143
|
+
kind: "template",
|
|
144
|
+
slug,
|
|
145
|
+
name: name || slug,
|
|
146
|
+
...(description ? { description } : {}),
|
|
147
|
+
...(route ? { route } : {}),
|
|
148
|
+
mode,
|
|
149
|
+
fields,
|
|
150
|
+
},
|
|
151
|
+
errors,
|
|
152
|
+
warnings,
|
|
153
|
+
};
|
|
215
154
|
}
|
|
216
|
-
|
|
217
|
-
* Parse field entries from the fields object body.
|
|
218
|
-
* Each entry is: fieldName: field.type({ ... }) or field.type()
|
|
219
|
-
*/
|
|
220
|
-
function parseFieldEntries(body, baseLine, errors, warnings) {
|
|
155
|
+
function parseFieldEntries(body, baseLine, errors, warnings, references) {
|
|
221
156
|
const fields = [];
|
|
222
|
-
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
157
|
+
const entries = parseObjectEntries(body);
|
|
158
|
+
for (const entry of entries) {
|
|
159
|
+
const definition = parseFieldExpression(entry.value, baseLine, entry.key, errors, warnings, references);
|
|
160
|
+
if (definition) {
|
|
161
|
+
fields.push({
|
|
162
|
+
name: entry.key,
|
|
163
|
+
...definition,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return fields;
|
|
168
|
+
}
|
|
169
|
+
function parseFieldExpression(expression, baseLine, fieldName, errors, warnings, references) {
|
|
170
|
+
const trimmed = expression.trim();
|
|
171
|
+
const match = /^field\s*\.\s*(\w+)\s*\(/.exec(trimmed);
|
|
172
|
+
if (!match) {
|
|
173
|
+
errors.push({
|
|
174
|
+
line: baseLine,
|
|
175
|
+
column: 0,
|
|
176
|
+
message: `Expected field builder for "${fieldName}"`,
|
|
177
|
+
});
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
const fieldType = match[1];
|
|
181
|
+
if (!FIELD_TYPE_SET.has(fieldType)) {
|
|
182
|
+
errors.push({
|
|
183
|
+
line: baseLine,
|
|
184
|
+
column: 0,
|
|
185
|
+
message: `Unknown field type "${fieldType}" for field "${fieldName}"`,
|
|
186
|
+
});
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
const argsBlock = extractBalancedFrom(trimmed, match[0].length - 1, "(", ")");
|
|
190
|
+
const rawArgs = argsBlock?.inner.trim() ?? "";
|
|
191
|
+
if (fieldType === "object") {
|
|
192
|
+
const options = parseFieldOptionsObject(rawArgs);
|
|
193
|
+
const nestedFieldsEntry = findObjectEntry(options, "fields");
|
|
194
|
+
if (!nestedFieldsEntry) {
|
|
195
|
+
warnings.push({
|
|
196
|
+
line: baseLine,
|
|
197
|
+
message: `Object field "${fieldName}" is missing nested fields`,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
type: "object",
|
|
202
|
+
required: parseBooleanValue(findObjectEntry(options, "required")?.value) ?? false,
|
|
203
|
+
label: parseStringValue(findObjectEntry(options, "label")?.value),
|
|
204
|
+
description: parseStringValue(findObjectEntry(options, "description")?.value),
|
|
205
|
+
fields: nestedFieldsEntry
|
|
206
|
+
? parseFieldEntries(unwrapObjectLiteral(nestedFieldsEntry.value), baseLine, errors, warnings, references)
|
|
207
|
+
: [],
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
if (fieldType === "array") {
|
|
211
|
+
const options = parseFieldOptionsObject(rawArgs);
|
|
212
|
+
const ofEntry = findObjectEntry(options, "of");
|
|
213
|
+
const of = ofEntry
|
|
214
|
+
? parseFieldExpression(ofEntry.value, baseLine, `${fieldName}[]`, errors, warnings, references)
|
|
215
|
+
: null;
|
|
216
|
+
if (!of) {
|
|
230
217
|
errors.push({
|
|
231
218
|
line: baseLine,
|
|
232
219
|
column: 0,
|
|
233
|
-
message: `
|
|
220
|
+
message: `Array field "${fieldName}" is missing a valid "of" definition`,
|
|
234
221
|
});
|
|
235
|
-
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
type: "array",
|
|
226
|
+
required: parseBooleanValue(findObjectEntry(options, "required")?.value) ?? false,
|
|
227
|
+
label: parseStringValue(findObjectEntry(options, "label")?.value),
|
|
228
|
+
description: parseStringValue(findObjectEntry(options, "description")?.value),
|
|
229
|
+
of,
|
|
230
|
+
minItems: parseNumberValue(findObjectEntry(options, "minItems")?.value),
|
|
231
|
+
maxItems: parseNumberValue(findObjectEntry(options, "maxItems")?.value),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
if (fieldType === "fragment") {
|
|
235
|
+
const args = splitTopLevel(rawArgs);
|
|
236
|
+
const fragmentToken = args[0]?.trim() ?? "";
|
|
237
|
+
const fragment = parseStringValue(fragmentToken) ??
|
|
238
|
+
references.get(fragmentToken)?.slug ??
|
|
239
|
+
fragmentToken.replace(/[^\w-]/g, "");
|
|
240
|
+
if (!fragment) {
|
|
241
|
+
errors.push({
|
|
242
|
+
line: baseLine,
|
|
243
|
+
column: 0,
|
|
244
|
+
message: `Fragment field "${fieldName}" is missing a fragment reference`,
|
|
245
|
+
});
|
|
246
|
+
return null;
|
|
236
247
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
required: false,
|
|
248
|
+
const options = args.length > 1 ? parseFieldOptionsObject(args.slice(1).join(",")) : [];
|
|
249
|
+
return {
|
|
250
|
+
type: "fragment",
|
|
251
|
+
required: parseBooleanValue(findObjectEntry(options, "required")?.value) ?? false,
|
|
252
|
+
label: parseStringValue(findObjectEntry(options, "label")?.value),
|
|
253
|
+
description: parseStringValue(findObjectEntry(options, "description")?.value),
|
|
254
|
+
fragment,
|
|
245
255
|
};
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
256
|
+
}
|
|
257
|
+
const options = rawArgs ? parseFieldOptionsObject(rawArgs) : [];
|
|
258
|
+
const label = parseStringValue(findObjectEntry(options, "label")?.value);
|
|
259
|
+
const description = parseStringValue(findObjectEntry(options, "description")?.value);
|
|
260
|
+
const required = parseBooleanValue(findObjectEntry(options, "required")?.value) ?? false;
|
|
261
|
+
if (fieldType === "select") {
|
|
262
|
+
const choices = parseChoicesArray(findObjectEntry(options, "choices")?.value);
|
|
263
|
+
return {
|
|
264
|
+
type: "select",
|
|
265
|
+
required,
|
|
266
|
+
...(label ? { label } : {}),
|
|
267
|
+
...(description ? { description } : {}),
|
|
268
|
+
...(choices ? { options: { choices } } : {}),
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
type: fieldType,
|
|
273
|
+
required,
|
|
274
|
+
...(label ? { label } : {}),
|
|
275
|
+
...(description ? { description } : {}),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function parseFieldOptionsObject(value) {
|
|
279
|
+
const trimmed = value.trim();
|
|
280
|
+
if (!trimmed) {
|
|
281
|
+
return [];
|
|
282
|
+
}
|
|
283
|
+
if (!trimmed.startsWith("{")) {
|
|
284
|
+
return [];
|
|
285
|
+
}
|
|
286
|
+
return parseObjectEntries(unwrapObjectLiteral(trimmed));
|
|
287
|
+
}
|
|
288
|
+
function parseChoicesArray(value) {
|
|
289
|
+
if (!value) {
|
|
290
|
+
return undefined;
|
|
291
|
+
}
|
|
292
|
+
const trimmed = value.trim();
|
|
293
|
+
if (!trimmed.startsWith("[")) {
|
|
294
|
+
return undefined;
|
|
295
|
+
}
|
|
296
|
+
const items = splitTopLevel(unwrapArrayLiteral(trimmed));
|
|
297
|
+
const choices = [];
|
|
298
|
+
for (const item of items) {
|
|
299
|
+
const entries = parseObjectEntries(unwrapObjectLiteral(item));
|
|
300
|
+
const label = parseStringValue(findObjectEntry(entries, "label")?.value);
|
|
301
|
+
const choiceValue = parseStringValue(findObjectEntry(entries, "value")?.value);
|
|
302
|
+
if (label !== undefined && choiceValue !== undefined) {
|
|
303
|
+
choices.push({ label, value: choiceValue });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return choices;
|
|
307
|
+
}
|
|
308
|
+
function parseObjectEntries(body) {
|
|
309
|
+
const entries = splitTopLevel(body);
|
|
310
|
+
const result = [];
|
|
311
|
+
for (const entry of entries) {
|
|
312
|
+
const separatorIndex = findTopLevelColon(entry);
|
|
313
|
+
if (separatorIndex === -1) {
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
const rawKey = entry.slice(0, separatorIndex).trim();
|
|
317
|
+
const rawValue = entry.slice(separatorIndex + 1).trim();
|
|
318
|
+
if (!rawKey || !rawValue) {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
result.push({
|
|
322
|
+
key: parseObjectKey(rawKey),
|
|
323
|
+
value: rawValue,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
328
|
+
function findObjectEntry(entries, key) {
|
|
329
|
+
return entries.find((entry) => entry.key === key);
|
|
330
|
+
}
|
|
331
|
+
function parseObjectKey(rawKey) {
|
|
332
|
+
return parseStringValue(rawKey) ?? rawKey;
|
|
333
|
+
}
|
|
334
|
+
function splitTopLevel(source) {
|
|
335
|
+
const parts = [];
|
|
336
|
+
let current = "";
|
|
337
|
+
let parenDepth = 0;
|
|
338
|
+
let braceDepth = 0;
|
|
339
|
+
let bracketDepth = 0;
|
|
340
|
+
let inString = null;
|
|
341
|
+
let escaped = false;
|
|
342
|
+
for (const char of source) {
|
|
343
|
+
if (escaped) {
|
|
344
|
+
current += char;
|
|
345
|
+
escaped = false;
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
if (inString) {
|
|
349
|
+
current += char;
|
|
350
|
+
if (char === "\\") {
|
|
351
|
+
escaped = true;
|
|
252
352
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if (descMatch) {
|
|
256
|
-
const descStart = descMatch.index + descMatch[0].length;
|
|
257
|
-
const descStr = parseStringLiteral(argsBody, skipWhitespace(argsBody, descStart));
|
|
258
|
-
if (descStr) {
|
|
259
|
-
fieldDef.description = descStr.value;
|
|
260
|
-
}
|
|
353
|
+
else if (char === inString) {
|
|
354
|
+
inString = null;
|
|
261
355
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
359
|
+
inString = char;
|
|
360
|
+
current += char;
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
if (char === "(")
|
|
364
|
+
parenDepth++;
|
|
365
|
+
if (char === ")")
|
|
366
|
+
parenDepth--;
|
|
367
|
+
if (char === "{")
|
|
368
|
+
braceDepth++;
|
|
369
|
+
if (char === "}")
|
|
370
|
+
braceDepth--;
|
|
371
|
+
if (char === "[")
|
|
372
|
+
bracketDepth++;
|
|
373
|
+
if (char === "]")
|
|
374
|
+
bracketDepth--;
|
|
375
|
+
if (char === "," &&
|
|
376
|
+
parenDepth === 0 &&
|
|
377
|
+
braceDepth === 0 &&
|
|
378
|
+
bracketDepth === 0) {
|
|
379
|
+
if (current.trim()) {
|
|
380
|
+
parts.push(current.trim());
|
|
268
381
|
}
|
|
382
|
+
current = "";
|
|
383
|
+
continue;
|
|
269
384
|
}
|
|
270
|
-
|
|
385
|
+
current += char;
|
|
271
386
|
}
|
|
272
|
-
|
|
387
|
+
if (current.trim()) {
|
|
388
|
+
parts.push(current.trim());
|
|
389
|
+
}
|
|
390
|
+
return parts;
|
|
273
391
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
392
|
+
function findTopLevelColon(source) {
|
|
393
|
+
let parenDepth = 0;
|
|
394
|
+
let braceDepth = 0;
|
|
395
|
+
let bracketDepth = 0;
|
|
396
|
+
let inString = null;
|
|
397
|
+
let escaped = false;
|
|
398
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
399
|
+
const char = source[index];
|
|
400
|
+
if (escaped) {
|
|
401
|
+
escaped = false;
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
if (inString) {
|
|
405
|
+
if (char === "\\") {
|
|
406
|
+
escaped = true;
|
|
407
|
+
}
|
|
408
|
+
else if (char === inString) {
|
|
409
|
+
inString = null;
|
|
410
|
+
}
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
414
|
+
inString = char;
|
|
293
415
|
continue;
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
416
|
+
}
|
|
417
|
+
if (char === "(")
|
|
418
|
+
parenDepth++;
|
|
419
|
+
if (char === ")")
|
|
420
|
+
parenDepth--;
|
|
421
|
+
if (char === "{")
|
|
422
|
+
braceDepth++;
|
|
423
|
+
if (char === "}")
|
|
424
|
+
braceDepth--;
|
|
425
|
+
if (char === "[")
|
|
426
|
+
bracketDepth++;
|
|
427
|
+
if (char === "]")
|
|
428
|
+
bracketDepth--;
|
|
429
|
+
if (char === ":" &&
|
|
430
|
+
parenDepth === 0 &&
|
|
431
|
+
braceDepth === 0 &&
|
|
432
|
+
bracketDepth === 0) {
|
|
433
|
+
return index;
|
|
298
434
|
}
|
|
299
435
|
}
|
|
300
|
-
return
|
|
436
|
+
return -1;
|
|
437
|
+
}
|
|
438
|
+
function parseStringValue(value) {
|
|
439
|
+
if (!value) {
|
|
440
|
+
return undefined;
|
|
441
|
+
}
|
|
442
|
+
return parseStringLiteral(value.trim(), 0)?.value;
|
|
443
|
+
}
|
|
444
|
+
function parseBooleanValue(value) {
|
|
445
|
+
if (!value) {
|
|
446
|
+
return undefined;
|
|
447
|
+
}
|
|
448
|
+
const trimmed = value.trim();
|
|
449
|
+
if (trimmed === "true")
|
|
450
|
+
return true;
|
|
451
|
+
if (trimmed === "false")
|
|
452
|
+
return false;
|
|
453
|
+
return undefined;
|
|
454
|
+
}
|
|
455
|
+
function parseNumberValue(value) {
|
|
456
|
+
if (!value) {
|
|
457
|
+
return undefined;
|
|
458
|
+
}
|
|
459
|
+
const parsed = Number(value.trim());
|
|
460
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
461
|
+
}
|
|
462
|
+
function unwrapObjectLiteral(value) {
|
|
463
|
+
const trimmed = value.trim();
|
|
464
|
+
const block = extractBalanced(trimmed, 0, "{", "}");
|
|
465
|
+
return block?.inner ?? "";
|
|
466
|
+
}
|
|
467
|
+
function unwrapArrayLiteral(value) {
|
|
468
|
+
const trimmed = value.trim();
|
|
469
|
+
const block = extractBalanced(trimmed, 0, "[", "]");
|
|
470
|
+
return block?.inner ?? "";
|
|
471
|
+
}
|
|
472
|
+
function stripComments(source) {
|
|
473
|
+
let result = "";
|
|
474
|
+
let index = 0;
|
|
475
|
+
let inString = null;
|
|
476
|
+
let escaped = false;
|
|
477
|
+
while (index < source.length) {
|
|
478
|
+
const char = source[index];
|
|
479
|
+
const next = source[index + 1];
|
|
480
|
+
if (escaped) {
|
|
481
|
+
result += char;
|
|
482
|
+
escaped = false;
|
|
483
|
+
index += 1;
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
if (inString) {
|
|
487
|
+
if (char === "\\") {
|
|
488
|
+
escaped = true;
|
|
489
|
+
result += char;
|
|
490
|
+
index += 1;
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
if (char === inString) {
|
|
494
|
+
inString = null;
|
|
495
|
+
}
|
|
496
|
+
result += char;
|
|
497
|
+
index += 1;
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
501
|
+
inString = char;
|
|
502
|
+
result += char;
|
|
503
|
+
index += 1;
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
if (char === "/" && next === "/") {
|
|
507
|
+
while (index < source.length && source[index] !== "\n") {
|
|
508
|
+
result += " ";
|
|
509
|
+
index += 1;
|
|
510
|
+
}
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
if (char === "/" && next === "*") {
|
|
514
|
+
index += 2;
|
|
515
|
+
while (index < source.length) {
|
|
516
|
+
if (source[index] === "*" && source[index + 1] === "/") {
|
|
517
|
+
index += 2;
|
|
518
|
+
break;
|
|
519
|
+
}
|
|
520
|
+
result += source[index] === "\n" ? "\n" : " ";
|
|
521
|
+
index += 1;
|
|
522
|
+
}
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
result += char;
|
|
526
|
+
index += 1;
|
|
527
|
+
}
|
|
528
|
+
return result;
|
|
529
|
+
}
|
|
530
|
+
function lineAt(source, index) {
|
|
531
|
+
let line = 1;
|
|
532
|
+
for (let i = 0; i < index && i < source.length; i += 1) {
|
|
533
|
+
if (source[i] === "\n") {
|
|
534
|
+
line += 1;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return line;
|
|
538
|
+
}
|
|
539
|
+
function columnAt(source, index) {
|
|
540
|
+
let column = 1;
|
|
541
|
+
for (let i = index - 1; i >= 0; i -= 1) {
|
|
542
|
+
if (source[i] === "\n") {
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
column += 1;
|
|
546
|
+
}
|
|
547
|
+
return column;
|
|
301
548
|
}
|
|
302
|
-
// === Utility functions ===
|
|
303
549
|
function skipWhitespace(source, index) {
|
|
304
|
-
|
|
305
|
-
|
|
550
|
+
let current = index;
|
|
551
|
+
while (current < source.length && /\s/.test(source[current])) {
|
|
552
|
+
current += 1;
|
|
306
553
|
}
|
|
307
|
-
return
|
|
554
|
+
return current;
|
|
308
555
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
*/
|
|
312
|
-
function parseStringLiteral(source, i) {
|
|
313
|
-
if (i >= source.length)
|
|
556
|
+
function parseStringLiteral(source, startIdx) {
|
|
557
|
+
if (startIdx >= source.length) {
|
|
314
558
|
return null;
|
|
315
|
-
|
|
316
|
-
|
|
559
|
+
}
|
|
560
|
+
const quote = source[startIdx];
|
|
561
|
+
if (quote !== '"' && quote !== "'" && quote !== "`") {
|
|
317
562
|
return null;
|
|
563
|
+
}
|
|
318
564
|
let value = "";
|
|
319
|
-
let
|
|
565
|
+
let index = startIdx + 1;
|
|
320
566
|
let escaped = false;
|
|
321
|
-
while (
|
|
322
|
-
const
|
|
567
|
+
while (index < source.length) {
|
|
568
|
+
const char = source[index];
|
|
323
569
|
if (escaped) {
|
|
324
|
-
switch (
|
|
570
|
+
switch (char) {
|
|
325
571
|
case "n":
|
|
326
572
|
value += "\n";
|
|
327
573
|
break;
|
|
@@ -332,88 +578,70 @@ function parseStringLiteral(source, i) {
|
|
|
332
578
|
value += "\\";
|
|
333
579
|
break;
|
|
334
580
|
default:
|
|
335
|
-
value +=
|
|
581
|
+
value += char;
|
|
336
582
|
break;
|
|
337
583
|
}
|
|
338
584
|
escaped = false;
|
|
339
|
-
|
|
585
|
+
index += 1;
|
|
340
586
|
continue;
|
|
341
587
|
}
|
|
342
|
-
if (
|
|
588
|
+
if (char === "\\") {
|
|
343
589
|
escaped = true;
|
|
344
|
-
|
|
590
|
+
index += 1;
|
|
345
591
|
continue;
|
|
346
592
|
}
|
|
347
|
-
if (
|
|
348
|
-
return { value, end:
|
|
593
|
+
if (char === quote) {
|
|
594
|
+
return { value, end: index + 1 };
|
|
349
595
|
}
|
|
350
|
-
value +=
|
|
351
|
-
|
|
596
|
+
value += char;
|
|
597
|
+
index += 1;
|
|
352
598
|
}
|
|
353
|
-
return null;
|
|
599
|
+
return null;
|
|
354
600
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
*/
|
|
358
|
-
function extractBalanced(source, i, open, close) {
|
|
359
|
-
if (source[i] !== open)
|
|
601
|
+
function extractBalanced(source, startIdx, open, close) {
|
|
602
|
+
if (source[startIdx] !== open) {
|
|
360
603
|
return null;
|
|
361
|
-
|
|
604
|
+
}
|
|
605
|
+
return extractBalancedFrom(source, startIdx, open, close);
|
|
362
606
|
}
|
|
363
|
-
function extractBalancedFrom(source,
|
|
607
|
+
function extractBalancedFrom(source, openIdx, open, close) {
|
|
364
608
|
let depth = 0;
|
|
365
609
|
let inString = null;
|
|
366
610
|
let escaped = false;
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
const
|
|
611
|
+
const contentStart = openIdx + 1;
|
|
612
|
+
for (let index = openIdx; index < source.length; index += 1) {
|
|
613
|
+
const char = source[index];
|
|
370
614
|
if (escaped) {
|
|
371
615
|
escaped = false;
|
|
372
|
-
i++;
|
|
373
616
|
continue;
|
|
374
617
|
}
|
|
375
618
|
if (inString) {
|
|
376
|
-
if (
|
|
619
|
+
if (char === "\\") {
|
|
377
620
|
escaped = true;
|
|
378
|
-
i++;
|
|
379
|
-
continue;
|
|
380
621
|
}
|
|
381
|
-
if (
|
|
622
|
+
else if (char === inString) {
|
|
382
623
|
inString = null;
|
|
383
624
|
}
|
|
384
|
-
i++;
|
|
385
625
|
continue;
|
|
386
626
|
}
|
|
387
|
-
if (
|
|
388
|
-
inString =
|
|
389
|
-
i++;
|
|
627
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
628
|
+
inString = char;
|
|
390
629
|
continue;
|
|
391
630
|
}
|
|
392
|
-
if (
|
|
393
|
-
depth
|
|
631
|
+
if (char === open) {
|
|
632
|
+
depth += 1;
|
|
633
|
+
continue;
|
|
394
634
|
}
|
|
395
|
-
|
|
396
|
-
depth
|
|
635
|
+
if (char === close) {
|
|
636
|
+
depth -= 1;
|
|
397
637
|
if (depth === 0) {
|
|
398
|
-
return {
|
|
638
|
+
return {
|
|
639
|
+
inner: source.slice(contentStart, index),
|
|
640
|
+
end: index + 1,
|
|
641
|
+
};
|
|
399
642
|
}
|
|
400
643
|
}
|
|
401
|
-
i++;
|
|
402
644
|
}
|
|
403
645
|
return null;
|
|
404
646
|
}
|
|
405
|
-
/**
|
|
406
|
-
* Extract a string property value from an object literal body.
|
|
407
|
-
* Matches: propName: "value" or propName: 'value'
|
|
408
|
-
*/
|
|
409
|
-
function extractPropertyString(body, propName) {
|
|
410
|
-
const pattern = new RegExp(`(?<![\\w])${propName}\\s*:\\s*`);
|
|
411
|
-
const match = pattern.exec(body);
|
|
412
|
-
if (!match)
|
|
413
|
-
return null;
|
|
414
|
-
const valueStart = match.index + match[0].length;
|
|
415
|
-
const i = skipWhitespace(body, valueStart);
|
|
416
|
-
const result = parseStringLiteral(body, i);
|
|
417
|
-
return result ? result.value : null;
|
|
418
|
-
}
|
|
419
647
|
//# sourceMappingURL=parse-schema.js.map
|