@objectstack/sdui-parser 17.2.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 +191 -0
- package/dist/index.d.mts +211 -6
- package/dist/index.d.ts +211 -6
- 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.js
CHANGED
|
@@ -20,9 +20,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
CONSUMED_WIDGET_OPTION_KEYS: () => CONSUMED_WIDGET_OPTION_KEYS,
|
|
24
|
+
DASHBOARD_WIDGET_HOST_TYPES: () => DASHBOARD_WIDGET_HOST_TYPES,
|
|
25
|
+
MANIFEST_INPUT_TYPES: () => MANIFEST_INPUT_TYPES,
|
|
26
|
+
UNCONSUMED_WIDGET_OPTION: () => UNCONSUMED_WIDGET_OPTION,
|
|
27
|
+
canonicalizeInputType: () => canonicalizeInputType,
|
|
28
|
+
checkDashboardWidgetOptions: () => checkDashboardWidgetOptions,
|
|
23
29
|
compile: () => compile,
|
|
24
30
|
generateBlockList: () => generateBlockList,
|
|
25
31
|
generateDts: () => generateDts,
|
|
32
|
+
inputTypeArms: () => inputTypeArms,
|
|
26
33
|
interpretBrace: () => interpretBrace,
|
|
27
34
|
manifestFromConfigs: () => manifestFromConfigs,
|
|
28
35
|
parseJsx: () => parseJsx,
|
|
@@ -34,6 +41,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
34
41
|
// src/parse.ts
|
|
35
42
|
var EVENT_ATTR = /^on[A-Z]/;
|
|
36
43
|
var FORBIDDEN_ATTRS = /* @__PURE__ */ new Set(["dangerouslySetInnerHTML", "ref", "key"]);
|
|
44
|
+
var DISCRIMINATOR_ATTR = "type";
|
|
37
45
|
function parseJsx(source, options = {}) {
|
|
38
46
|
return new Parser(source, options).parseDocument();
|
|
39
47
|
}
|
|
@@ -80,7 +88,7 @@ var Parser = class {
|
|
|
80
88
|
if (c === "" || c === ">" || c === "/") break;
|
|
81
89
|
const attr = this.parseAttr(start, tag);
|
|
82
90
|
if (!attr) break;
|
|
83
|
-
props[attr.name] = attr.value;
|
|
91
|
+
if (!attr.drop) props[attr.name] = attr.value;
|
|
84
92
|
}
|
|
85
93
|
this.skipWs();
|
|
86
94
|
let children;
|
|
@@ -91,7 +99,7 @@ var Parser = class {
|
|
|
91
99
|
} else {
|
|
92
100
|
this.error("unterminated-open-tag", `Unterminated <${tag}> open tag`, start, tag);
|
|
93
101
|
}
|
|
94
|
-
const node = { type: tag
|
|
102
|
+
const node = { ...props, type: tag };
|
|
95
103
|
if (children && children.length) node.children = children;
|
|
96
104
|
return node;
|
|
97
105
|
}
|
|
@@ -108,6 +116,15 @@ var Parser = class {
|
|
|
108
116
|
this.skipWs();
|
|
109
117
|
value = this.parseAttrValue(tag);
|
|
110
118
|
}
|
|
119
|
+
if (name === DISCRIMINATOR_ATTR) {
|
|
120
|
+
this.error(
|
|
121
|
+
"forbidden-attr",
|
|
122
|
+
`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.`,
|
|
123
|
+
elStart,
|
|
124
|
+
tag
|
|
125
|
+
);
|
|
126
|
+
return { name, value: void 0, drop: true };
|
|
127
|
+
}
|
|
111
128
|
if (EVENT_ATTR.test(name) || FORBIDDEN_ATTRS.has(name)) {
|
|
112
129
|
this.error("forbidden-attr", `Attribute "${name}" is not allowed on <${tag}>`, elStart, tag);
|
|
113
130
|
return { name: `__forbidden_${name}`, value: void 0 };
|
|
@@ -165,8 +182,17 @@ var Parser = class {
|
|
|
165
182
|
continue;
|
|
166
183
|
}
|
|
167
184
|
const text = this.readTextRun();
|
|
168
|
-
const
|
|
169
|
-
|
|
185
|
+
const collapsed = text.replace(/\s+/g, " ");
|
|
186
|
+
const core = collapsed.trim();
|
|
187
|
+
const afterSibling = children.length > 0;
|
|
188
|
+
const beforeElement = this.peek() === "<" && !this.src.startsWith("</", this.pos);
|
|
189
|
+
if (core) {
|
|
190
|
+
const lead = afterSibling && collapsed.startsWith(" ") ? " " : "";
|
|
191
|
+
const trail = beforeElement && collapsed.endsWith(" ") ? " " : "";
|
|
192
|
+
children.push(`${lead}${core}${trail}`);
|
|
193
|
+
} else if (collapsed && afterSibling && beforeElement) {
|
|
194
|
+
children.push(" ");
|
|
195
|
+
}
|
|
170
196
|
}
|
|
171
197
|
return children;
|
|
172
198
|
}
|
|
@@ -250,9 +276,268 @@ function interpretBrace(raw) {
|
|
|
250
276
|
try {
|
|
251
277
|
return JSON.parse(trimmed);
|
|
252
278
|
} catch {
|
|
253
|
-
|
|
279
|
+
const literal = readLiteral(trimmed);
|
|
280
|
+
return literal === NOT_LITERAL ? { $expr: trimmed } : literal;
|
|
254
281
|
}
|
|
255
282
|
}
|
|
283
|
+
var NOT_LITERAL = /* @__PURE__ */ Symbol("not-a-literal");
|
|
284
|
+
var LITERAL_WS = /[ \t\n\r]/;
|
|
285
|
+
var IDENT_START = /[A-Za-z_$]/;
|
|
286
|
+
var IDENT_CHAR = /[A-Za-z0-9_$]/;
|
|
287
|
+
var NUMBER = /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/;
|
|
288
|
+
var SIMPLE_ESCAPE = {
|
|
289
|
+
'"': '"',
|
|
290
|
+
"\\": "\\",
|
|
291
|
+
"/": "/",
|
|
292
|
+
b: "\b",
|
|
293
|
+
f: "\f",
|
|
294
|
+
n: "\n",
|
|
295
|
+
r: "\r",
|
|
296
|
+
t: " "
|
|
297
|
+
};
|
|
298
|
+
function readLiteral(src) {
|
|
299
|
+
const reader = new LiteralReader(src);
|
|
300
|
+
const value = reader.value();
|
|
301
|
+
if (value === NOT_LITERAL) return NOT_LITERAL;
|
|
302
|
+
reader.ws();
|
|
303
|
+
return reader.done() ? value : NOT_LITERAL;
|
|
304
|
+
}
|
|
305
|
+
var LiteralReader = class {
|
|
306
|
+
constructor(src) {
|
|
307
|
+
this.src = src;
|
|
308
|
+
this.pos = 0;
|
|
309
|
+
}
|
|
310
|
+
done() {
|
|
311
|
+
return this.pos >= this.src.length;
|
|
312
|
+
}
|
|
313
|
+
ws() {
|
|
314
|
+
while (this.pos < this.src.length && LITERAL_WS.test(this.src[this.pos])) this.pos++;
|
|
315
|
+
}
|
|
316
|
+
value() {
|
|
317
|
+
this.ws();
|
|
318
|
+
const c = this.src[this.pos];
|
|
319
|
+
if (c === void 0) return NOT_LITERAL;
|
|
320
|
+
if (c === '"' || c === "'") return this.string(c);
|
|
321
|
+
if (c === "[") return this.array();
|
|
322
|
+
if (c === "{") return this.object();
|
|
323
|
+
if (this.keyword("true")) return true;
|
|
324
|
+
if (this.keyword("false")) return false;
|
|
325
|
+
if (this.keyword("null")) return null;
|
|
326
|
+
return this.number();
|
|
327
|
+
}
|
|
328
|
+
/** A keyword only when it is not the prefix of a longer identifier. */
|
|
329
|
+
keyword(word) {
|
|
330
|
+
if (!this.src.startsWith(word, this.pos)) return false;
|
|
331
|
+
const after = this.src[this.pos + word.length];
|
|
332
|
+
if (after !== void 0 && IDENT_CHAR.test(after)) return false;
|
|
333
|
+
this.pos += word.length;
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
number() {
|
|
337
|
+
const m = NUMBER.exec(this.src.slice(this.pos));
|
|
338
|
+
if (!m) return NOT_LITERAL;
|
|
339
|
+
this.pos += m[0].length;
|
|
340
|
+
return Number(m[0]);
|
|
341
|
+
}
|
|
342
|
+
string(quote) {
|
|
343
|
+
this.pos++;
|
|
344
|
+
let out = "";
|
|
345
|
+
for (; ; ) {
|
|
346
|
+
const c = this.src[this.pos];
|
|
347
|
+
if (c === void 0) return NOT_LITERAL;
|
|
348
|
+
if (c === quote) {
|
|
349
|
+
this.pos++;
|
|
350
|
+
return out;
|
|
351
|
+
}
|
|
352
|
+
if (c === "\\") {
|
|
353
|
+
const esc = this.src[this.pos + 1];
|
|
354
|
+
if (esc === void 0) return NOT_LITERAL;
|
|
355
|
+
if (esc === "u") {
|
|
356
|
+
const hex = this.src.slice(this.pos + 2, this.pos + 6);
|
|
357
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex)) return NOT_LITERAL;
|
|
358
|
+
out += String.fromCharCode(parseInt(hex, 16));
|
|
359
|
+
this.pos += 6;
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
if (esc === "'" && quote === "'") {
|
|
363
|
+
out += "'";
|
|
364
|
+
this.pos += 2;
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
const simple = SIMPLE_ESCAPE[esc];
|
|
368
|
+
if (simple === void 0) return NOT_LITERAL;
|
|
369
|
+
out += simple;
|
|
370
|
+
this.pos += 2;
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (c < " ") return NOT_LITERAL;
|
|
374
|
+
out += c;
|
|
375
|
+
this.pos++;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
array() {
|
|
379
|
+
this.pos++;
|
|
380
|
+
const out = [];
|
|
381
|
+
this.ws();
|
|
382
|
+
if (this.src[this.pos] === "]") {
|
|
383
|
+
this.pos++;
|
|
384
|
+
return out;
|
|
385
|
+
}
|
|
386
|
+
for (; ; ) {
|
|
387
|
+
const item = this.value();
|
|
388
|
+
if (item === NOT_LITERAL) return NOT_LITERAL;
|
|
389
|
+
out.push(item);
|
|
390
|
+
this.ws();
|
|
391
|
+
const c = this.src[this.pos];
|
|
392
|
+
if (c === ",") {
|
|
393
|
+
this.pos++;
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
if (c === "]") {
|
|
397
|
+
this.pos++;
|
|
398
|
+
return out;
|
|
399
|
+
}
|
|
400
|
+
return NOT_LITERAL;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
object() {
|
|
404
|
+
this.pos++;
|
|
405
|
+
const out = {};
|
|
406
|
+
this.ws();
|
|
407
|
+
if (this.src[this.pos] === "}") {
|
|
408
|
+
this.pos++;
|
|
409
|
+
return out;
|
|
410
|
+
}
|
|
411
|
+
for (; ; ) {
|
|
412
|
+
this.ws();
|
|
413
|
+
const key = this.key();
|
|
414
|
+
if (key === NOT_LITERAL) return NOT_LITERAL;
|
|
415
|
+
this.ws();
|
|
416
|
+
if (this.src[this.pos] !== ":") return NOT_LITERAL;
|
|
417
|
+
this.pos++;
|
|
418
|
+
const item = this.value();
|
|
419
|
+
if (item === NOT_LITERAL) return NOT_LITERAL;
|
|
420
|
+
Object.defineProperty(out, key, {
|
|
421
|
+
value: item,
|
|
422
|
+
writable: true,
|
|
423
|
+
enumerable: true,
|
|
424
|
+
configurable: true
|
|
425
|
+
});
|
|
426
|
+
this.ws();
|
|
427
|
+
const c = this.src[this.pos];
|
|
428
|
+
if (c === ",") {
|
|
429
|
+
this.pos++;
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
if (c === "}") {
|
|
433
|
+
this.pos++;
|
|
434
|
+
return out;
|
|
435
|
+
}
|
|
436
|
+
return NOT_LITERAL;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
/** A quoted string, or a bare identifier — the second ruled widening. */
|
|
440
|
+
key() {
|
|
441
|
+
const c = this.src[this.pos];
|
|
442
|
+
if (c === '"' || c === "'") return this.string(c);
|
|
443
|
+
if (c !== void 0 && IDENT_START.test(c)) {
|
|
444
|
+
const start = this.pos;
|
|
445
|
+
this.pos++;
|
|
446
|
+
while (this.pos < this.src.length && IDENT_CHAR.test(this.src[this.pos])) this.pos++;
|
|
447
|
+
return this.src.slice(start, this.pos);
|
|
448
|
+
}
|
|
449
|
+
return NOT_LITERAL;
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
// src/input-type.ts
|
|
454
|
+
var MANIFEST_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
455
|
+
"string",
|
|
456
|
+
"number",
|
|
457
|
+
"boolean",
|
|
458
|
+
"enum",
|
|
459
|
+
"array",
|
|
460
|
+
"object",
|
|
461
|
+
"color",
|
|
462
|
+
"date",
|
|
463
|
+
"code",
|
|
464
|
+
"file",
|
|
465
|
+
"slot"
|
|
466
|
+
]);
|
|
467
|
+
function inputTypeArms(type) {
|
|
468
|
+
if (type === void 0) return [];
|
|
469
|
+
return Array.isArray(type) ? type : [type];
|
|
470
|
+
}
|
|
471
|
+
function canonicalizeInputType(type) {
|
|
472
|
+
const arms = (Array.isArray(type) ? type : [type]).filter(
|
|
473
|
+
(arm) => typeof arm === "string" && MANIFEST_INPUT_TYPES.has(arm)
|
|
474
|
+
);
|
|
475
|
+
const distinct = [...new Set(arms)];
|
|
476
|
+
if (distinct.length === 0) return "string";
|
|
477
|
+
if (distinct.length === 1) return distinct[0];
|
|
478
|
+
return distinct;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// src/dashboard-widget-options.ts
|
|
482
|
+
var UNCONSUMED_WIDGET_OPTION = "unconsumed-widget-option";
|
|
483
|
+
var DASHBOARD_WIDGET_HOST_TYPES = /* @__PURE__ */ new Set([
|
|
484
|
+
"dashboard",
|
|
485
|
+
"dashboard-grid"
|
|
486
|
+
]);
|
|
487
|
+
var CONSUMED_WIDGET_OPTION_KEYS = [
|
|
488
|
+
"dateGranularity",
|
|
489
|
+
"description",
|
|
490
|
+
"limit",
|
|
491
|
+
"sortBy",
|
|
492
|
+
"sortOrder",
|
|
493
|
+
"stageOrder"
|
|
494
|
+
];
|
|
495
|
+
var CONSUMED = new Set(CONSUMED_WIDGET_OPTION_KEYS);
|
|
496
|
+
var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
497
|
+
var isExpr = (v) => isPlainObject(v) && "$expr" in v;
|
|
498
|
+
function checkDashboardWidgetOptions(node) {
|
|
499
|
+
if (!DASHBOARD_WIDGET_HOST_TYPES.has(node.type)) return [];
|
|
500
|
+
const widgets = node.widgets;
|
|
501
|
+
if (!Array.isArray(widgets)) return [];
|
|
502
|
+
const diagnostics = [];
|
|
503
|
+
widgets.forEach((widget, index) => {
|
|
504
|
+
if (!isPlainObject(widget) || isExpr(widget)) return;
|
|
505
|
+
if (widget.component !== void 0) return;
|
|
506
|
+
if (widget.dataset === void 0 || widget.dataset === null || widget.dataset === "") return;
|
|
507
|
+
const options = widget.options;
|
|
508
|
+
if (!isPlainObject(options) || isExpr(options)) return;
|
|
509
|
+
if (Array.isArray(widget.suppressWarnings) && widget.suppressWarnings.includes(UNCONSUMED_WIDGET_OPTION)) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const label = typeof widget.id === "string" && widget.id !== "" ? widget.id : `#${index}`;
|
|
513
|
+
const widgetType = typeof widget.type === "string" && widget.type !== "" ? widget.type : "widget";
|
|
514
|
+
for (const key of Object.keys(options)) {
|
|
515
|
+
if (CONSUMED.has(key)) continue;
|
|
516
|
+
diagnostics.push({
|
|
517
|
+
severity: "warning",
|
|
518
|
+
// DIVERGENCE FROM OBJECTUI, and the only one below this file's header:
|
|
519
|
+
// objectui writes `code: UNCONSUMED_WIDGET_OPTION` here. This repo runs
|
|
520
|
+
// `check:dispatcher-error-vocabulary`, whose `objlitconst` shape reads
|
|
521
|
+
// the SCREAMING_SNAKE constant NAME at a `code:` position and then must
|
|
522
|
+
// reduce it to a literal — and its literal grammar is
|
|
523
|
+
// `[A-Za-z][A-Za-z0-9_]*`, which a KEBAB-case value cannot satisfy. So
|
|
524
|
+
// the constant form is reported as an unresolvable code constant, and
|
|
525
|
+
// that finding cannot be declared away. `unconsumed-widget-option` is a
|
|
526
|
+
// parser DIAGNOSTIC code, not an ADR-0112 wire code, and an inline
|
|
527
|
+
// quoted literal is the form both vocabulary gates already accept for
|
|
528
|
+
// the six sibling diagnostic codes in `validate.ts`
|
|
529
|
+
// (`unknown-component`, `unknown-prop`, `not-a-container`,
|
|
530
|
+
// `inert-expression`, `type-mismatch`, `invalid-enum`). The emitted
|
|
531
|
+
// VALUE is unchanged, and the test next door pins it equal to
|
|
532
|
+
// `UNCONSUMED_WIDGET_OPTION` so the two spellings cannot drift apart.
|
|
533
|
+
code: "unconsumed-widget-option",
|
|
534
|
+
message: `<${node.type}> widget "${label}" (${widgetType}): options.${key} reaches no renderer \u2014 dashboard widget renderers read only: ${CONSUMED_WIDGET_OPTION_KEYS.join(", ")}`,
|
|
535
|
+
tag: node.type
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
return diagnostics;
|
|
540
|
+
}
|
|
256
541
|
|
|
257
542
|
// src/validate.ts
|
|
258
543
|
var BASE_PROPS = /* @__PURE__ */ new Set([
|
|
@@ -266,7 +551,7 @@ var BASE_PROPS = /* @__PURE__ */ new Set([
|
|
|
266
551
|
"disabledOn",
|
|
267
552
|
"children"
|
|
268
553
|
]);
|
|
269
|
-
var
|
|
554
|
+
var isExpr2 = (v) => typeof v === "object" && v !== null && "$expr" in v;
|
|
270
555
|
function validateTree(tree, manifest) {
|
|
271
556
|
const diagnostics = [];
|
|
272
557
|
const requires = /* @__PURE__ */ new Set();
|
|
@@ -309,7 +594,14 @@ function validateTree(tree, manifest) {
|
|
|
309
594
|
if (input.binding) {
|
|
310
595
|
bindings.push({ tag: node.type, input: key, kind: input.binding, value });
|
|
311
596
|
}
|
|
312
|
-
if (
|
|
597
|
+
if (isExpr2(value)) {
|
|
598
|
+
diagnostics.push({
|
|
599
|
+
severity: "warning",
|
|
600
|
+
code: "inert-expression",
|
|
601
|
+
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`,
|
|
602
|
+
tag: node.type
|
|
603
|
+
});
|
|
604
|
+
} else {
|
|
313
605
|
const typeDiag = checkType(node.type, input, value);
|
|
314
606
|
if (typeDiag) diagnostics.push(typeDiag);
|
|
315
607
|
}
|
|
@@ -322,46 +614,70 @@ function validateTree(tree, manifest) {
|
|
|
322
614
|
tag: node.type
|
|
323
615
|
});
|
|
324
616
|
}
|
|
617
|
+
diagnostics.push(...checkDashboardWidgetOptions(node));
|
|
325
618
|
}
|
|
326
619
|
if (node.children) node.children.forEach(visit);
|
|
327
620
|
};
|
|
328
621
|
if (tree) visit(tree);
|
|
329
622
|
return { diagnostics, requires: [...requires], bindings };
|
|
330
623
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
code: "type-mismatch",
|
|
335
|
-
message: `<${tag}> prop "${input.name}" expected ${expected}`,
|
|
336
|
-
tag
|
|
337
|
-
});
|
|
338
|
-
switch (input.type) {
|
|
624
|
+
var enumValues = (input) => (input.enum ?? []).map((e) => typeof e === "object" ? e.value : e);
|
|
625
|
+
function armAccepts(arm, input, value) {
|
|
626
|
+
switch (arm) {
|
|
339
627
|
case "number":
|
|
340
|
-
return typeof value === "number"
|
|
628
|
+
return typeof value === "number";
|
|
341
629
|
case "boolean":
|
|
342
|
-
return typeof value === "boolean"
|
|
630
|
+
return typeof value === "boolean";
|
|
343
631
|
case "string":
|
|
344
632
|
case "color":
|
|
345
633
|
case "date":
|
|
346
634
|
case "code":
|
|
347
635
|
case "file":
|
|
348
|
-
return typeof value === "string"
|
|
636
|
+
return typeof value === "string";
|
|
349
637
|
case "array":
|
|
350
|
-
return Array.isArray(value)
|
|
638
|
+
return Array.isArray(value);
|
|
351
639
|
case "object":
|
|
352
|
-
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
353
|
-
case "enum":
|
|
354
|
-
|
|
355
|
-
return allowed.includes(value) ? null : {
|
|
356
|
-
severity: "error",
|
|
357
|
-
code: "invalid-enum",
|
|
358
|
-
message: `<${tag}> prop "${input.name}"=${JSON.stringify(value)} is not one of ${JSON.stringify(allowed)}`,
|
|
359
|
-
tag
|
|
360
|
-
};
|
|
361
|
-
}
|
|
640
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
641
|
+
case "enum":
|
|
642
|
+
return enumValues(input).includes(value);
|
|
362
643
|
default:
|
|
363
|
-
return
|
|
644
|
+
return true;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
function armExpectation(arm, input) {
|
|
648
|
+
switch (arm) {
|
|
649
|
+
case "number":
|
|
650
|
+
return "a number";
|
|
651
|
+
case "boolean":
|
|
652
|
+
return "a boolean";
|
|
653
|
+
case "array":
|
|
654
|
+
return "an array";
|
|
655
|
+
case "object":
|
|
656
|
+
return "an object";
|
|
657
|
+
case "enum":
|
|
658
|
+
return `one of ${JSON.stringify(enumValues(input))}`;
|
|
659
|
+
default:
|
|
660
|
+
return "a string";
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
function checkType(tag, input, value) {
|
|
664
|
+
const arms = inputTypeArms(input.type);
|
|
665
|
+
if (arms.length === 0) return null;
|
|
666
|
+
if (arms.some((arm) => armAccepts(arm, input, value))) return null;
|
|
667
|
+
if (arms.length === 1 && arms[0] === "enum") {
|
|
668
|
+
return {
|
|
669
|
+
severity: "error",
|
|
670
|
+
code: "invalid-enum",
|
|
671
|
+
message: `<${tag}> prop "${input.name}"=${JSON.stringify(value)} is not one of ${JSON.stringify(enumValues(input))}`,
|
|
672
|
+
tag
|
|
673
|
+
};
|
|
364
674
|
}
|
|
675
|
+
return {
|
|
676
|
+
severity: arms.includes("enum") ? "error" : "warning",
|
|
677
|
+
code: "type-mismatch",
|
|
678
|
+
message: `<${tag}> prop "${input.name}" expected ${arms.map((arm) => armExpectation(arm, input)).join(" or ")}`,
|
|
679
|
+
tag
|
|
680
|
+
};
|
|
365
681
|
}
|
|
366
682
|
|
|
367
683
|
// src/codegen.ts
|
|
@@ -378,7 +694,6 @@ function generateDts(manifest, options = {}) {
|
|
|
378
694
|
interface ElementChildrenAttribute { children: object; }` : "";
|
|
379
695
|
return `// AUTO-GENERATED by @object-ui/sdui-parser \u2014 DO NOT EDIT.
|
|
380
696
|
// Source of truth: ComponentRegistry inputs (ADR-0080 \xA73). Regenerate via codegen.
|
|
381
|
-
/* eslint-disable */
|
|
382
697
|
|
|
383
698
|
export interface SduiBaseProps {
|
|
384
699
|
id?: string;
|
|
@@ -405,17 +720,18 @@ export {};
|
|
|
405
720
|
`;
|
|
406
721
|
}
|
|
407
722
|
function emitInterface(comp) {
|
|
408
|
-
const lines = comp.inputs.filter((i) => i.
|
|
723
|
+
const lines = comp.inputs.filter((i) => valueArms(i).length > 0).map((i) => ` ${propLine(i)}`).join("\n");
|
|
409
724
|
return `export interface ${propsName(comp.type)} extends SduiBaseProps {
|
|
410
725
|
${lines}
|
|
411
726
|
}`;
|
|
412
727
|
}
|
|
728
|
+
var valueArms = (input) => inputTypeArms(input.type).filter((arm) => arm !== "slot");
|
|
413
729
|
function propLine(input) {
|
|
414
730
|
const opt = input.required ? "" : "?";
|
|
415
731
|
return `${quoteKeyIfNeeded(input.name)}${opt}: ${tsType(input)};`;
|
|
416
732
|
}
|
|
417
|
-
function
|
|
418
|
-
switch (
|
|
733
|
+
function armTsType(arm, input) {
|
|
734
|
+
switch (arm) {
|
|
419
735
|
case "number":
|
|
420
736
|
return "number";
|
|
421
737
|
case "boolean":
|
|
@@ -428,15 +744,16 @@ function tsType(input) {
|
|
|
428
744
|
const vals = (input.enum ?? []).map((e) => typeof e === "object" ? e.value : e);
|
|
429
745
|
return vals.length ? vals.map((v) => JSON.stringify(v)).join(" | ") : "string";
|
|
430
746
|
}
|
|
431
|
-
case "string":
|
|
432
|
-
case "color":
|
|
433
|
-
case "date":
|
|
434
|
-
case "code":
|
|
435
|
-
case "file":
|
|
436
747
|
default:
|
|
437
748
|
return "string";
|
|
438
749
|
}
|
|
439
750
|
}
|
|
751
|
+
function tsType(input) {
|
|
752
|
+
const arms = valueArms(input);
|
|
753
|
+
if (arms.length === 0) return "string";
|
|
754
|
+
const emitted = [...new Set(arms.map((arm) => armTsType(arm, input)))];
|
|
755
|
+
return emitted.join(" | ");
|
|
756
|
+
}
|
|
440
757
|
var IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
441
758
|
var quoteKeyIfNeeded = (name) => IDENT.test(name) ? name : JSON.stringify(name);
|
|
442
759
|
function propsName(type) {
|
|
@@ -475,19 +792,6 @@ function compile(source, manifest) {
|
|
|
475
792
|
ok: !diagnostics.some((d) => d.severity === "error")
|
|
476
793
|
};
|
|
477
794
|
}
|
|
478
|
-
var INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
479
|
-
"string",
|
|
480
|
-
"number",
|
|
481
|
-
"boolean",
|
|
482
|
-
"enum",
|
|
483
|
-
"array",
|
|
484
|
-
"object",
|
|
485
|
-
"color",
|
|
486
|
-
"date",
|
|
487
|
-
"code",
|
|
488
|
-
"file",
|
|
489
|
-
"slot"
|
|
490
|
-
]);
|
|
491
795
|
function manifestFromConfigs(configs, opts = {}) {
|
|
492
796
|
const components = {};
|
|
493
797
|
for (const c of configs) {
|
|
@@ -499,7 +803,7 @@ function manifestFromConfigs(configs, opts = {}) {
|
|
|
499
803
|
isContainer: c.isContainer,
|
|
500
804
|
inputs: (c.inputs ?? []).map((i) => ({
|
|
501
805
|
name: i.name,
|
|
502
|
-
type:
|
|
806
|
+
type: canonicalizeInputType(i.type),
|
|
503
807
|
required: i.required,
|
|
504
808
|
enum: i.enum,
|
|
505
809
|
binding: i.binding,
|
|
@@ -511,9 +815,16 @@ function manifestFromConfigs(configs, opts = {}) {
|
|
|
511
815
|
}
|
|
512
816
|
// Annotate the CommonJS export names for ESM import in node:
|
|
513
817
|
0 && (module.exports = {
|
|
818
|
+
CONSUMED_WIDGET_OPTION_KEYS,
|
|
819
|
+
DASHBOARD_WIDGET_HOST_TYPES,
|
|
820
|
+
MANIFEST_INPUT_TYPES,
|
|
821
|
+
UNCONSUMED_WIDGET_OPTION,
|
|
822
|
+
canonicalizeInputType,
|
|
823
|
+
checkDashboardWidgetOptions,
|
|
514
824
|
compile,
|
|
515
825
|
generateBlockList,
|
|
516
826
|
generateDts,
|
|
827
|
+
inputTypeArms,
|
|
517
828
|
interpretBrace,
|
|
518
829
|
manifestFromConfigs,
|
|
519
830
|
parseJsx,
|