@objectstack/sdui-parser 17.1.0 → 17.3.0
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/CHANGELOG.md +193 -0
- package/dist/index.d.mts +212 -7
- package/dist/index.d.ts +212 -7
- package/dist/index.js +364 -53
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +357 -53
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/parse.ts
|
|
2
2
|
var EVENT_ATTR = /^on[A-Z]/;
|
|
3
3
|
var FORBIDDEN_ATTRS = /* @__PURE__ */ new Set(["dangerouslySetInnerHTML", "ref", "key"]);
|
|
4
|
+
var DISCRIMINATOR_ATTR = "type";
|
|
4
5
|
function parseJsx(source, options = {}) {
|
|
5
6
|
return new Parser(source, options).parseDocument();
|
|
6
7
|
}
|
|
@@ -47,7 +48,7 @@ var Parser = class {
|
|
|
47
48
|
if (c === "" || c === ">" || c === "/") break;
|
|
48
49
|
const attr = this.parseAttr(start, tag);
|
|
49
50
|
if (!attr) break;
|
|
50
|
-
props[attr.name] = attr.value;
|
|
51
|
+
if (!attr.drop) props[attr.name] = attr.value;
|
|
51
52
|
}
|
|
52
53
|
this.skipWs();
|
|
53
54
|
let children;
|
|
@@ -58,7 +59,7 @@ var Parser = class {
|
|
|
58
59
|
} else {
|
|
59
60
|
this.error("unterminated-open-tag", `Unterminated <${tag}> open tag`, start, tag);
|
|
60
61
|
}
|
|
61
|
-
const node = { type: tag
|
|
62
|
+
const node = { ...props, type: tag };
|
|
62
63
|
if (children && children.length) node.children = children;
|
|
63
64
|
return node;
|
|
64
65
|
}
|
|
@@ -75,6 +76,15 @@ var Parser = class {
|
|
|
75
76
|
this.skipWs();
|
|
76
77
|
value = this.parseAttrValue(tag);
|
|
77
78
|
}
|
|
79
|
+
if (name === DISCRIMINATOR_ATTR) {
|
|
80
|
+
this.error(
|
|
81
|
+
"forbidden-attr",
|
|
82
|
+
`Attribute "${DISCRIMINATOR_ATTR}" is not allowed on <${tag}> \u2014 on this tier the tag name IS the component, so <${tag}> already means type "${tag}". Delete the attribute, or write the tag of the component you meant.`,
|
|
83
|
+
elStart,
|
|
84
|
+
tag
|
|
85
|
+
);
|
|
86
|
+
return { name, value: void 0, drop: true };
|
|
87
|
+
}
|
|
78
88
|
if (EVENT_ATTR.test(name) || FORBIDDEN_ATTRS.has(name)) {
|
|
79
89
|
this.error("forbidden-attr", `Attribute "${name}" is not allowed on <${tag}>`, elStart, tag);
|
|
80
90
|
return { name: `__forbidden_${name}`, value: void 0 };
|
|
@@ -132,8 +142,17 @@ var Parser = class {
|
|
|
132
142
|
continue;
|
|
133
143
|
}
|
|
134
144
|
const text = this.readTextRun();
|
|
135
|
-
const
|
|
136
|
-
|
|
145
|
+
const collapsed = text.replace(/\s+/g, " ");
|
|
146
|
+
const core = collapsed.trim();
|
|
147
|
+
const afterSibling = children.length > 0;
|
|
148
|
+
const beforeElement = this.peek() === "<" && !this.src.startsWith("</", this.pos);
|
|
149
|
+
if (core) {
|
|
150
|
+
const lead = afterSibling && collapsed.startsWith(" ") ? " " : "";
|
|
151
|
+
const trail = beforeElement && collapsed.endsWith(" ") ? " " : "";
|
|
152
|
+
children.push(`${lead}${core}${trail}`);
|
|
153
|
+
} else if (collapsed && afterSibling && beforeElement) {
|
|
154
|
+
children.push(" ");
|
|
155
|
+
}
|
|
137
156
|
}
|
|
138
157
|
return children;
|
|
139
158
|
}
|
|
@@ -217,9 +236,268 @@ function interpretBrace(raw) {
|
|
|
217
236
|
try {
|
|
218
237
|
return JSON.parse(trimmed);
|
|
219
238
|
} catch {
|
|
220
|
-
|
|
239
|
+
const literal = readLiteral(trimmed);
|
|
240
|
+
return literal === NOT_LITERAL ? { $expr: trimmed } : literal;
|
|
221
241
|
}
|
|
222
242
|
}
|
|
243
|
+
var NOT_LITERAL = /* @__PURE__ */ Symbol("not-a-literal");
|
|
244
|
+
var LITERAL_WS = /[ \t\n\r]/;
|
|
245
|
+
var IDENT_START = /[A-Za-z_$]/;
|
|
246
|
+
var IDENT_CHAR = /[A-Za-z0-9_$]/;
|
|
247
|
+
var NUMBER = /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/;
|
|
248
|
+
var SIMPLE_ESCAPE = {
|
|
249
|
+
'"': '"',
|
|
250
|
+
"\\": "\\",
|
|
251
|
+
"/": "/",
|
|
252
|
+
b: "\b",
|
|
253
|
+
f: "\f",
|
|
254
|
+
n: "\n",
|
|
255
|
+
r: "\r",
|
|
256
|
+
t: " "
|
|
257
|
+
};
|
|
258
|
+
function readLiteral(src) {
|
|
259
|
+
const reader = new LiteralReader(src);
|
|
260
|
+
const value = reader.value();
|
|
261
|
+
if (value === NOT_LITERAL) return NOT_LITERAL;
|
|
262
|
+
reader.ws();
|
|
263
|
+
return reader.done() ? value : NOT_LITERAL;
|
|
264
|
+
}
|
|
265
|
+
var LiteralReader = class {
|
|
266
|
+
constructor(src) {
|
|
267
|
+
this.src = src;
|
|
268
|
+
this.pos = 0;
|
|
269
|
+
}
|
|
270
|
+
done() {
|
|
271
|
+
return this.pos >= this.src.length;
|
|
272
|
+
}
|
|
273
|
+
ws() {
|
|
274
|
+
while (this.pos < this.src.length && LITERAL_WS.test(this.src[this.pos])) this.pos++;
|
|
275
|
+
}
|
|
276
|
+
value() {
|
|
277
|
+
this.ws();
|
|
278
|
+
const c = this.src[this.pos];
|
|
279
|
+
if (c === void 0) return NOT_LITERAL;
|
|
280
|
+
if (c === '"' || c === "'") return this.string(c);
|
|
281
|
+
if (c === "[") return this.array();
|
|
282
|
+
if (c === "{") return this.object();
|
|
283
|
+
if (this.keyword("true")) return true;
|
|
284
|
+
if (this.keyword("false")) return false;
|
|
285
|
+
if (this.keyword("null")) return null;
|
|
286
|
+
return this.number();
|
|
287
|
+
}
|
|
288
|
+
/** A keyword only when it is not the prefix of a longer identifier. */
|
|
289
|
+
keyword(word) {
|
|
290
|
+
if (!this.src.startsWith(word, this.pos)) return false;
|
|
291
|
+
const after = this.src[this.pos + word.length];
|
|
292
|
+
if (after !== void 0 && IDENT_CHAR.test(after)) return false;
|
|
293
|
+
this.pos += word.length;
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
number() {
|
|
297
|
+
const m = NUMBER.exec(this.src.slice(this.pos));
|
|
298
|
+
if (!m) return NOT_LITERAL;
|
|
299
|
+
this.pos += m[0].length;
|
|
300
|
+
return Number(m[0]);
|
|
301
|
+
}
|
|
302
|
+
string(quote) {
|
|
303
|
+
this.pos++;
|
|
304
|
+
let out = "";
|
|
305
|
+
for (; ; ) {
|
|
306
|
+
const c = this.src[this.pos];
|
|
307
|
+
if (c === void 0) return NOT_LITERAL;
|
|
308
|
+
if (c === quote) {
|
|
309
|
+
this.pos++;
|
|
310
|
+
return out;
|
|
311
|
+
}
|
|
312
|
+
if (c === "\\") {
|
|
313
|
+
const esc = this.src[this.pos + 1];
|
|
314
|
+
if (esc === void 0) return NOT_LITERAL;
|
|
315
|
+
if (esc === "u") {
|
|
316
|
+
const hex = this.src.slice(this.pos + 2, this.pos + 6);
|
|
317
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex)) return NOT_LITERAL;
|
|
318
|
+
out += String.fromCharCode(parseInt(hex, 16));
|
|
319
|
+
this.pos += 6;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (esc === "'" && quote === "'") {
|
|
323
|
+
out += "'";
|
|
324
|
+
this.pos += 2;
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
const simple = SIMPLE_ESCAPE[esc];
|
|
328
|
+
if (simple === void 0) return NOT_LITERAL;
|
|
329
|
+
out += simple;
|
|
330
|
+
this.pos += 2;
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
if (c < " ") return NOT_LITERAL;
|
|
334
|
+
out += c;
|
|
335
|
+
this.pos++;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
array() {
|
|
339
|
+
this.pos++;
|
|
340
|
+
const out = [];
|
|
341
|
+
this.ws();
|
|
342
|
+
if (this.src[this.pos] === "]") {
|
|
343
|
+
this.pos++;
|
|
344
|
+
return out;
|
|
345
|
+
}
|
|
346
|
+
for (; ; ) {
|
|
347
|
+
const item = this.value();
|
|
348
|
+
if (item === NOT_LITERAL) return NOT_LITERAL;
|
|
349
|
+
out.push(item);
|
|
350
|
+
this.ws();
|
|
351
|
+
const c = this.src[this.pos];
|
|
352
|
+
if (c === ",") {
|
|
353
|
+
this.pos++;
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
if (c === "]") {
|
|
357
|
+
this.pos++;
|
|
358
|
+
return out;
|
|
359
|
+
}
|
|
360
|
+
return NOT_LITERAL;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
object() {
|
|
364
|
+
this.pos++;
|
|
365
|
+
const out = {};
|
|
366
|
+
this.ws();
|
|
367
|
+
if (this.src[this.pos] === "}") {
|
|
368
|
+
this.pos++;
|
|
369
|
+
return out;
|
|
370
|
+
}
|
|
371
|
+
for (; ; ) {
|
|
372
|
+
this.ws();
|
|
373
|
+
const key = this.key();
|
|
374
|
+
if (key === NOT_LITERAL) return NOT_LITERAL;
|
|
375
|
+
this.ws();
|
|
376
|
+
if (this.src[this.pos] !== ":") return NOT_LITERAL;
|
|
377
|
+
this.pos++;
|
|
378
|
+
const item = this.value();
|
|
379
|
+
if (item === NOT_LITERAL) return NOT_LITERAL;
|
|
380
|
+
Object.defineProperty(out, key, {
|
|
381
|
+
value: item,
|
|
382
|
+
writable: true,
|
|
383
|
+
enumerable: true,
|
|
384
|
+
configurable: true
|
|
385
|
+
});
|
|
386
|
+
this.ws();
|
|
387
|
+
const c = this.src[this.pos];
|
|
388
|
+
if (c === ",") {
|
|
389
|
+
this.pos++;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (c === "}") {
|
|
393
|
+
this.pos++;
|
|
394
|
+
return out;
|
|
395
|
+
}
|
|
396
|
+
return NOT_LITERAL;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
/** A quoted string, or a bare identifier — the second ruled widening. */
|
|
400
|
+
key() {
|
|
401
|
+
const c = this.src[this.pos];
|
|
402
|
+
if (c === '"' || c === "'") return this.string(c);
|
|
403
|
+
if (c !== void 0 && IDENT_START.test(c)) {
|
|
404
|
+
const start = this.pos;
|
|
405
|
+
this.pos++;
|
|
406
|
+
while (this.pos < this.src.length && IDENT_CHAR.test(this.src[this.pos])) this.pos++;
|
|
407
|
+
return this.src.slice(start, this.pos);
|
|
408
|
+
}
|
|
409
|
+
return NOT_LITERAL;
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
// src/input-type.ts
|
|
414
|
+
var MANIFEST_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
415
|
+
"string",
|
|
416
|
+
"number",
|
|
417
|
+
"boolean",
|
|
418
|
+
"enum",
|
|
419
|
+
"array",
|
|
420
|
+
"object",
|
|
421
|
+
"color",
|
|
422
|
+
"date",
|
|
423
|
+
"code",
|
|
424
|
+
"file",
|
|
425
|
+
"slot"
|
|
426
|
+
]);
|
|
427
|
+
function inputTypeArms(type) {
|
|
428
|
+
if (type === void 0) return [];
|
|
429
|
+
return Array.isArray(type) ? type : [type];
|
|
430
|
+
}
|
|
431
|
+
function canonicalizeInputType(type) {
|
|
432
|
+
const arms = (Array.isArray(type) ? type : [type]).filter(
|
|
433
|
+
(arm) => typeof arm === "string" && MANIFEST_INPUT_TYPES.has(arm)
|
|
434
|
+
);
|
|
435
|
+
const distinct = [...new Set(arms)];
|
|
436
|
+
if (distinct.length === 0) return "string";
|
|
437
|
+
if (distinct.length === 1) return distinct[0];
|
|
438
|
+
return distinct;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// src/dashboard-widget-options.ts
|
|
442
|
+
var UNCONSUMED_WIDGET_OPTION = "unconsumed-widget-option";
|
|
443
|
+
var DASHBOARD_WIDGET_HOST_TYPES = /* @__PURE__ */ new Set([
|
|
444
|
+
"dashboard",
|
|
445
|
+
"dashboard-grid"
|
|
446
|
+
]);
|
|
447
|
+
var CONSUMED_WIDGET_OPTION_KEYS = [
|
|
448
|
+
"dateGranularity",
|
|
449
|
+
"description",
|
|
450
|
+
"limit",
|
|
451
|
+
"sortBy",
|
|
452
|
+
"sortOrder",
|
|
453
|
+
"stageOrder"
|
|
454
|
+
];
|
|
455
|
+
var CONSUMED = new Set(CONSUMED_WIDGET_OPTION_KEYS);
|
|
456
|
+
var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
457
|
+
var isExpr = (v) => isPlainObject(v) && "$expr" in v;
|
|
458
|
+
function checkDashboardWidgetOptions(node) {
|
|
459
|
+
if (!DASHBOARD_WIDGET_HOST_TYPES.has(node.type)) return [];
|
|
460
|
+
const widgets = node.widgets;
|
|
461
|
+
if (!Array.isArray(widgets)) return [];
|
|
462
|
+
const diagnostics = [];
|
|
463
|
+
widgets.forEach((widget, index) => {
|
|
464
|
+
if (!isPlainObject(widget) || isExpr(widget)) return;
|
|
465
|
+
if (widget.component !== void 0) return;
|
|
466
|
+
if (widget.dataset === void 0 || widget.dataset === null || widget.dataset === "") return;
|
|
467
|
+
const options = widget.options;
|
|
468
|
+
if (!isPlainObject(options) || isExpr(options)) return;
|
|
469
|
+
if (Array.isArray(widget.suppressWarnings) && widget.suppressWarnings.includes(UNCONSUMED_WIDGET_OPTION)) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const label = typeof widget.id === "string" && widget.id !== "" ? widget.id : `#${index}`;
|
|
473
|
+
const widgetType = typeof widget.type === "string" && widget.type !== "" ? widget.type : "widget";
|
|
474
|
+
for (const key of Object.keys(options)) {
|
|
475
|
+
if (CONSUMED.has(key)) continue;
|
|
476
|
+
diagnostics.push({
|
|
477
|
+
severity: "warning",
|
|
478
|
+
// DIVERGENCE FROM OBJECTUI, and the only one below this file's header:
|
|
479
|
+
// objectui writes `code: UNCONSUMED_WIDGET_OPTION` here. This repo runs
|
|
480
|
+
// `check:dispatcher-error-vocabulary`, whose `objlitconst` shape reads
|
|
481
|
+
// the SCREAMING_SNAKE constant NAME at a `code:` position and then must
|
|
482
|
+
// reduce it to a literal — and its literal grammar is
|
|
483
|
+
// `[A-Za-z][A-Za-z0-9_]*`, which a KEBAB-case value cannot satisfy. So
|
|
484
|
+
// the constant form is reported as an unresolvable code constant, and
|
|
485
|
+
// that finding cannot be declared away. `unconsumed-widget-option` is a
|
|
486
|
+
// parser DIAGNOSTIC code, not an ADR-0112 wire code, and an inline
|
|
487
|
+
// quoted literal is the form both vocabulary gates already accept for
|
|
488
|
+
// the six sibling diagnostic codes in `validate.ts`
|
|
489
|
+
// (`unknown-component`, `unknown-prop`, `not-a-container`,
|
|
490
|
+
// `inert-expression`, `type-mismatch`, `invalid-enum`). The emitted
|
|
491
|
+
// VALUE is unchanged, and the test next door pins it equal to
|
|
492
|
+
// `UNCONSUMED_WIDGET_OPTION` so the two spellings cannot drift apart.
|
|
493
|
+
code: "unconsumed-widget-option",
|
|
494
|
+
message: `<${node.type}> widget "${label}" (${widgetType}): options.${key} reaches no renderer \u2014 dashboard widget renderers read only: ${CONSUMED_WIDGET_OPTION_KEYS.join(", ")}`,
|
|
495
|
+
tag: node.type
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
return diagnostics;
|
|
500
|
+
}
|
|
223
501
|
|
|
224
502
|
// src/validate.ts
|
|
225
503
|
var BASE_PROPS = /* @__PURE__ */ new Set([
|
|
@@ -233,7 +511,7 @@ var BASE_PROPS = /* @__PURE__ */ new Set([
|
|
|
233
511
|
"disabledOn",
|
|
234
512
|
"children"
|
|
235
513
|
]);
|
|
236
|
-
var
|
|
514
|
+
var isExpr2 = (v) => typeof v === "object" && v !== null && "$expr" in v;
|
|
237
515
|
function validateTree(tree, manifest) {
|
|
238
516
|
const diagnostics = [];
|
|
239
517
|
const requires = /* @__PURE__ */ new Set();
|
|
@@ -276,7 +554,14 @@ function validateTree(tree, manifest) {
|
|
|
276
554
|
if (input.binding) {
|
|
277
555
|
bindings.push({ tag: node.type, input: key, kind: input.binding, value });
|
|
278
556
|
}
|
|
279
|
-
if (
|
|
557
|
+
if (isExpr2(value)) {
|
|
558
|
+
diagnostics.push({
|
|
559
|
+
severity: "warning",
|
|
560
|
+
code: "inert-expression",
|
|
561
|
+
message: `<${node.type}> prop "${key}" is a braced expression this tier never evaluates \u2014 the value will be silently ignored at render. This tier materializes LITERALS only (strings, numbers, booleans, null, arrays, objects; quotes may be single or double, object keys may be unquoted), e.g. columns={['name','amount']} works \u2014 columns={rows.map((r) => r.name)} cannot`,
|
|
562
|
+
tag: node.type
|
|
563
|
+
});
|
|
564
|
+
} else {
|
|
280
565
|
const typeDiag = checkType(node.type, input, value);
|
|
281
566
|
if (typeDiag) diagnostics.push(typeDiag);
|
|
282
567
|
}
|
|
@@ -289,46 +574,70 @@ function validateTree(tree, manifest) {
|
|
|
289
574
|
tag: node.type
|
|
290
575
|
});
|
|
291
576
|
}
|
|
577
|
+
diagnostics.push(...checkDashboardWidgetOptions(node));
|
|
292
578
|
}
|
|
293
579
|
if (node.children) node.children.forEach(visit);
|
|
294
580
|
};
|
|
295
581
|
if (tree) visit(tree);
|
|
296
582
|
return { diagnostics, requires: [...requires], bindings };
|
|
297
583
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
code: "type-mismatch",
|
|
302
|
-
message: `<${tag}> prop "${input.name}" expected ${expected}`,
|
|
303
|
-
tag
|
|
304
|
-
});
|
|
305
|
-
switch (input.type) {
|
|
584
|
+
var enumValues = (input) => (input.enum ?? []).map((e) => typeof e === "object" ? e.value : e);
|
|
585
|
+
function armAccepts(arm, input, value) {
|
|
586
|
+
switch (arm) {
|
|
306
587
|
case "number":
|
|
307
|
-
return typeof value === "number"
|
|
588
|
+
return typeof value === "number";
|
|
308
589
|
case "boolean":
|
|
309
|
-
return typeof value === "boolean"
|
|
590
|
+
return typeof value === "boolean";
|
|
310
591
|
case "string":
|
|
311
592
|
case "color":
|
|
312
593
|
case "date":
|
|
313
594
|
case "code":
|
|
314
595
|
case "file":
|
|
315
|
-
return typeof value === "string"
|
|
596
|
+
return typeof value === "string";
|
|
316
597
|
case "array":
|
|
317
|
-
return Array.isArray(value)
|
|
598
|
+
return Array.isArray(value);
|
|
318
599
|
case "object":
|
|
319
|
-
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
320
|
-
case "enum":
|
|
321
|
-
|
|
322
|
-
return allowed.includes(value) ? null : {
|
|
323
|
-
severity: "error",
|
|
324
|
-
code: "invalid-enum",
|
|
325
|
-
message: `<${tag}> prop "${input.name}"=${JSON.stringify(value)} is not one of ${JSON.stringify(allowed)}`,
|
|
326
|
-
tag
|
|
327
|
-
};
|
|
328
|
-
}
|
|
600
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
601
|
+
case "enum":
|
|
602
|
+
return enumValues(input).includes(value);
|
|
329
603
|
default:
|
|
330
|
-
return
|
|
604
|
+
return true;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
function armExpectation(arm, input) {
|
|
608
|
+
switch (arm) {
|
|
609
|
+
case "number":
|
|
610
|
+
return "a number";
|
|
611
|
+
case "boolean":
|
|
612
|
+
return "a boolean";
|
|
613
|
+
case "array":
|
|
614
|
+
return "an array";
|
|
615
|
+
case "object":
|
|
616
|
+
return "an object";
|
|
617
|
+
case "enum":
|
|
618
|
+
return `one of ${JSON.stringify(enumValues(input))}`;
|
|
619
|
+
default:
|
|
620
|
+
return "a string";
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
function checkType(tag, input, value) {
|
|
624
|
+
const arms = inputTypeArms(input.type);
|
|
625
|
+
if (arms.length === 0) return null;
|
|
626
|
+
if (arms.some((arm) => armAccepts(arm, input, value))) return null;
|
|
627
|
+
if (arms.length === 1 && arms[0] === "enum") {
|
|
628
|
+
return {
|
|
629
|
+
severity: "error",
|
|
630
|
+
code: "invalid-enum",
|
|
631
|
+
message: `<${tag}> prop "${input.name}"=${JSON.stringify(value)} is not one of ${JSON.stringify(enumValues(input))}`,
|
|
632
|
+
tag
|
|
633
|
+
};
|
|
331
634
|
}
|
|
635
|
+
return {
|
|
636
|
+
severity: arms.includes("enum") ? "error" : "warning",
|
|
637
|
+
code: "type-mismatch",
|
|
638
|
+
message: `<${tag}> prop "${input.name}" expected ${arms.map((arm) => armExpectation(arm, input)).join(" or ")}`,
|
|
639
|
+
tag
|
|
640
|
+
};
|
|
332
641
|
}
|
|
333
642
|
|
|
334
643
|
// src/codegen.ts
|
|
@@ -345,7 +654,6 @@ function generateDts(manifest, options = {}) {
|
|
|
345
654
|
interface ElementChildrenAttribute { children: object; }` : "";
|
|
346
655
|
return `// AUTO-GENERATED by @object-ui/sdui-parser \u2014 DO NOT EDIT.
|
|
347
656
|
// Source of truth: ComponentRegistry inputs (ADR-0080 \xA73). Regenerate via codegen.
|
|
348
|
-
/* eslint-disable */
|
|
349
657
|
|
|
350
658
|
export interface SduiBaseProps {
|
|
351
659
|
id?: string;
|
|
@@ -372,17 +680,18 @@ export {};
|
|
|
372
680
|
`;
|
|
373
681
|
}
|
|
374
682
|
function emitInterface(comp) {
|
|
375
|
-
const lines = comp.inputs.filter((i) => i.
|
|
683
|
+
const lines = comp.inputs.filter((i) => valueArms(i).length > 0).map((i) => ` ${propLine(i)}`).join("\n");
|
|
376
684
|
return `export interface ${propsName(comp.type)} extends SduiBaseProps {
|
|
377
685
|
${lines}
|
|
378
686
|
}`;
|
|
379
687
|
}
|
|
688
|
+
var valueArms = (input) => inputTypeArms(input.type).filter((arm) => arm !== "slot");
|
|
380
689
|
function propLine(input) {
|
|
381
690
|
const opt = input.required ? "" : "?";
|
|
382
691
|
return `${quoteKeyIfNeeded(input.name)}${opt}: ${tsType(input)};`;
|
|
383
692
|
}
|
|
384
|
-
function
|
|
385
|
-
switch (
|
|
693
|
+
function armTsType(arm, input) {
|
|
694
|
+
switch (arm) {
|
|
386
695
|
case "number":
|
|
387
696
|
return "number";
|
|
388
697
|
case "boolean":
|
|
@@ -395,15 +704,16 @@ function tsType(input) {
|
|
|
395
704
|
const vals = (input.enum ?? []).map((e) => typeof e === "object" ? e.value : e);
|
|
396
705
|
return vals.length ? vals.map((v) => JSON.stringify(v)).join(" | ") : "string";
|
|
397
706
|
}
|
|
398
|
-
case "string":
|
|
399
|
-
case "color":
|
|
400
|
-
case "date":
|
|
401
|
-
case "code":
|
|
402
|
-
case "file":
|
|
403
707
|
default:
|
|
404
708
|
return "string";
|
|
405
709
|
}
|
|
406
710
|
}
|
|
711
|
+
function tsType(input) {
|
|
712
|
+
const arms = valueArms(input);
|
|
713
|
+
if (arms.length === 0) return "string";
|
|
714
|
+
const emitted = [...new Set(arms.map((arm) => armTsType(arm, input)))];
|
|
715
|
+
return emitted.join(" | ");
|
|
716
|
+
}
|
|
407
717
|
var IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
408
718
|
var quoteKeyIfNeeded = (name) => IDENT.test(name) ? name : JSON.stringify(name);
|
|
409
719
|
function propsName(type) {
|
|
@@ -442,19 +752,6 @@ function compile(source, manifest) {
|
|
|
442
752
|
ok: !diagnostics.some((d) => d.severity === "error")
|
|
443
753
|
};
|
|
444
754
|
}
|
|
445
|
-
var INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
446
|
-
"string",
|
|
447
|
-
"number",
|
|
448
|
-
"boolean",
|
|
449
|
-
"enum",
|
|
450
|
-
"array",
|
|
451
|
-
"object",
|
|
452
|
-
"color",
|
|
453
|
-
"date",
|
|
454
|
-
"code",
|
|
455
|
-
"file",
|
|
456
|
-
"slot"
|
|
457
|
-
]);
|
|
458
755
|
function manifestFromConfigs(configs, opts = {}) {
|
|
459
756
|
const components = {};
|
|
460
757
|
for (const c of configs) {
|
|
@@ -466,7 +763,7 @@ function manifestFromConfigs(configs, opts = {}) {
|
|
|
466
763
|
isContainer: c.isContainer,
|
|
467
764
|
inputs: (c.inputs ?? []).map((i) => ({
|
|
468
765
|
name: i.name,
|
|
469
|
-
type:
|
|
766
|
+
type: canonicalizeInputType(i.type),
|
|
470
767
|
required: i.required,
|
|
471
768
|
enum: i.enum,
|
|
472
769
|
binding: i.binding,
|
|
@@ -477,9 +774,16 @@ function manifestFromConfigs(configs, opts = {}) {
|
|
|
477
774
|
return { components };
|
|
478
775
|
}
|
|
479
776
|
export {
|
|
777
|
+
CONSUMED_WIDGET_OPTION_KEYS,
|
|
778
|
+
DASHBOARD_WIDGET_HOST_TYPES,
|
|
779
|
+
MANIFEST_INPUT_TYPES,
|
|
780
|
+
UNCONSUMED_WIDGET_OPTION,
|
|
781
|
+
canonicalizeInputType,
|
|
782
|
+
checkDashboardWidgetOptions,
|
|
480
783
|
compile,
|
|
481
784
|
generateBlockList,
|
|
482
785
|
generateDts,
|
|
786
|
+
inputTypeArms,
|
|
483
787
|
interpretBrace,
|
|
484
788
|
manifestFromConfigs,
|
|
485
789
|
parseJsx,
|