@constela/compiler 0.4.0 → 0.6.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/dist/index.d.ts +173 -5
- package/dist/index.js +1087 -12
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -27,18 +27,104 @@ import {
|
|
|
27
27
|
createSchemaError,
|
|
28
28
|
createOperationInvalidForTypeError,
|
|
29
29
|
createOperationMissingFieldError,
|
|
30
|
-
|
|
30
|
+
createUndefinedRouteParamError,
|
|
31
|
+
createRouteNotDefinedError,
|
|
32
|
+
createUndefinedImportError,
|
|
33
|
+
createImportsNotDefinedError,
|
|
34
|
+
createInvalidDataSourceError,
|
|
35
|
+
createUndefinedDataSourceError,
|
|
36
|
+
createDataNotDefinedError,
|
|
37
|
+
createUndefinedDataError,
|
|
38
|
+
createInvalidStorageOperationError,
|
|
39
|
+
createInvalidStorageTypeError,
|
|
40
|
+
createStorageSetMissingValueError,
|
|
41
|
+
createInvalidClipboardOperationError,
|
|
42
|
+
createClipboardWriteMissingValueError,
|
|
43
|
+
createInvalidNavigateTargetError,
|
|
44
|
+
createUndefinedRefError,
|
|
45
|
+
isEventHandler,
|
|
46
|
+
DATA_SOURCE_TYPES,
|
|
47
|
+
DATA_TRANSFORMS,
|
|
48
|
+
STORAGE_OPERATIONS,
|
|
49
|
+
STORAGE_TYPES,
|
|
50
|
+
CLIPBOARD_OPERATIONS,
|
|
51
|
+
NAVIGATE_TARGETS
|
|
31
52
|
} from "@constela/core";
|
|
32
53
|
function buildPath(base, ...segments) {
|
|
33
54
|
return segments.reduce((p, s) => `${p}/${s}`, base);
|
|
34
55
|
}
|
|
35
|
-
function
|
|
36
|
-
const
|
|
37
|
-
const
|
|
56
|
+
function extractRouteParams(path) {
|
|
57
|
+
const params = [];
|
|
58
|
+
const segments = path.split("/");
|
|
59
|
+
for (const segment of segments) {
|
|
60
|
+
if (segment.startsWith(":")) {
|
|
61
|
+
params.push(segment.slice(1));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return params;
|
|
65
|
+
}
|
|
66
|
+
function collectRefs(node) {
|
|
67
|
+
const refs = /* @__PURE__ */ new Set();
|
|
68
|
+
switch (node.kind) {
|
|
69
|
+
case "element":
|
|
70
|
+
if (node.ref) {
|
|
71
|
+
refs.add(node.ref);
|
|
72
|
+
}
|
|
73
|
+
if (node.children) {
|
|
74
|
+
for (const child of node.children) {
|
|
75
|
+
for (const ref of collectRefs(child)) {
|
|
76
|
+
refs.add(ref);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
case "if":
|
|
82
|
+
for (const ref of collectRefs(node.then)) {
|
|
83
|
+
refs.add(ref);
|
|
84
|
+
}
|
|
85
|
+
if (node.else) {
|
|
86
|
+
for (const ref of collectRefs(node.else)) {
|
|
87
|
+
refs.add(ref);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
break;
|
|
91
|
+
case "each":
|
|
92
|
+
for (const ref of collectRefs(node.body)) {
|
|
93
|
+
refs.add(ref);
|
|
94
|
+
}
|
|
95
|
+
break;
|
|
96
|
+
case "component":
|
|
97
|
+
if (node.children) {
|
|
98
|
+
for (const child of node.children) {
|
|
99
|
+
for (const ref of collectRefs(child)) {
|
|
100
|
+
refs.add(ref);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
case "text":
|
|
106
|
+
case "slot":
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
return refs;
|
|
110
|
+
}
|
|
111
|
+
function collectContext(programAst) {
|
|
112
|
+
const stateNames = new Set(Object.keys(programAst.state));
|
|
113
|
+
const actionNames = new Set(programAst.actions.map((a) => a.name));
|
|
38
114
|
const componentNames = new Set(
|
|
39
|
-
|
|
115
|
+
programAst.components ? Object.keys(programAst.components) : []
|
|
116
|
+
);
|
|
117
|
+
const routeParams = new Set(
|
|
118
|
+
programAst.route ? extractRouteParams(programAst.route.path) : []
|
|
40
119
|
);
|
|
41
|
-
|
|
120
|
+
const importNames = new Set(
|
|
121
|
+
programAst.imports ? Object.keys(programAst.imports) : []
|
|
122
|
+
);
|
|
123
|
+
const dataNames = new Set(
|
|
124
|
+
programAst.data ? Object.keys(programAst.data) : []
|
|
125
|
+
);
|
|
126
|
+
const refNames = collectRefs(programAst.view);
|
|
127
|
+
return { stateNames, actionNames, componentNames, routeParams, importNames, dataNames, refNames };
|
|
42
128
|
}
|
|
43
129
|
function checkDuplicateActions(ast2) {
|
|
44
130
|
const errors = [];
|
|
@@ -71,6 +157,39 @@ function validateExpression(expr, path, context, scope, paramScope) {
|
|
|
71
157
|
errors.push(createUndefinedParamError(expr.name, path));
|
|
72
158
|
}
|
|
73
159
|
break;
|
|
160
|
+
case "route": {
|
|
161
|
+
if (!hasRoute) {
|
|
162
|
+
errors.push(createRouteNotDefinedError(path));
|
|
163
|
+
} else {
|
|
164
|
+
const source = expr.source ?? "param";
|
|
165
|
+
if (source === "param" && !context.routeParams.has(expr.name)) {
|
|
166
|
+
errors.push(createUndefinedRouteParamError(expr.name, path));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case "import": {
|
|
172
|
+
if (!hasImports) {
|
|
173
|
+
errors.push(createImportsNotDefinedError(path));
|
|
174
|
+
} else if (!context.importNames.has(expr.name)) {
|
|
175
|
+
errors.push(createUndefinedImportError(expr.name, path));
|
|
176
|
+
}
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
case "data": {
|
|
180
|
+
if (!hasData) {
|
|
181
|
+
errors.push(createDataNotDefinedError(path));
|
|
182
|
+
} else if (!context.dataNames.has(expr.name)) {
|
|
183
|
+
errors.push(createUndefinedDataError(expr.name, path));
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
case "ref": {
|
|
188
|
+
if (!context.refNames.has(expr.name)) {
|
|
189
|
+
errors.push(createUndefinedRefError(expr.name, path));
|
|
190
|
+
}
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
74
193
|
case "bin":
|
|
75
194
|
errors.push(...validateExpression(expr.left, buildPath(path, "left"), context, scope, paramScope));
|
|
76
195
|
errors.push(...validateExpression(expr.right, buildPath(path, "right"), context, scope, paramScope));
|
|
@@ -174,6 +293,161 @@ function validateActionStep(step, path, context) {
|
|
|
174
293
|
}
|
|
175
294
|
}
|
|
176
295
|
break;
|
|
296
|
+
case "storage": {
|
|
297
|
+
const storageStep = step;
|
|
298
|
+
if (!STORAGE_OPERATIONS.includes(storageStep.operation)) {
|
|
299
|
+
errors.push(createInvalidStorageOperationError(storageStep.operation, path));
|
|
300
|
+
}
|
|
301
|
+
if (!STORAGE_TYPES.includes(storageStep.storage)) {
|
|
302
|
+
errors.push(createInvalidStorageTypeError(storageStep.storage, path));
|
|
303
|
+
}
|
|
304
|
+
errors.push(
|
|
305
|
+
...validateExpressionStateOnly(storageStep.key, buildPath(path, "key"), context)
|
|
306
|
+
);
|
|
307
|
+
if (storageStep.operation === "set" && !storageStep.value) {
|
|
308
|
+
errors.push(createStorageSetMissingValueError(path));
|
|
309
|
+
}
|
|
310
|
+
if (storageStep.value) {
|
|
311
|
+
errors.push(
|
|
312
|
+
...validateExpressionStateOnly(storageStep.value, buildPath(path, "value"), context)
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
if (storageStep.onSuccess) {
|
|
316
|
+
for (let i = 0; i < storageStep.onSuccess.length; i++) {
|
|
317
|
+
const successStep = storageStep.onSuccess[i];
|
|
318
|
+
if (successStep === void 0) continue;
|
|
319
|
+
errors.push(
|
|
320
|
+
...validateActionStep(successStep, buildPath(path, "onSuccess", i), context)
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (storageStep.onError) {
|
|
325
|
+
for (let i = 0; i < storageStep.onError.length; i++) {
|
|
326
|
+
const errorStep = storageStep.onError[i];
|
|
327
|
+
if (errorStep === void 0) continue;
|
|
328
|
+
errors.push(
|
|
329
|
+
...validateActionStep(errorStep, buildPath(path, "onError", i), context)
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
case "clipboard": {
|
|
336
|
+
const clipboardStep = step;
|
|
337
|
+
if (!CLIPBOARD_OPERATIONS.includes(clipboardStep.operation)) {
|
|
338
|
+
errors.push(createInvalidClipboardOperationError(clipboardStep.operation, path));
|
|
339
|
+
}
|
|
340
|
+
if (clipboardStep.operation === "write" && !clipboardStep.value) {
|
|
341
|
+
errors.push(createClipboardWriteMissingValueError(path));
|
|
342
|
+
}
|
|
343
|
+
if (clipboardStep.value) {
|
|
344
|
+
errors.push(
|
|
345
|
+
...validateExpressionStateOnly(clipboardStep.value, buildPath(path, "value"), context)
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
if (clipboardStep.onSuccess) {
|
|
349
|
+
for (let i = 0; i < clipboardStep.onSuccess.length; i++) {
|
|
350
|
+
const successStep = clipboardStep.onSuccess[i];
|
|
351
|
+
if (successStep === void 0) continue;
|
|
352
|
+
errors.push(
|
|
353
|
+
...validateActionStep(successStep, buildPath(path, "onSuccess", i), context)
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (clipboardStep.onError) {
|
|
358
|
+
for (let i = 0; i < clipboardStep.onError.length; i++) {
|
|
359
|
+
const errorStep = clipboardStep.onError[i];
|
|
360
|
+
if (errorStep === void 0) continue;
|
|
361
|
+
errors.push(
|
|
362
|
+
...validateActionStep(errorStep, buildPath(path, "onError", i), context)
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
case "navigate": {
|
|
369
|
+
const navigateStep = step;
|
|
370
|
+
errors.push(
|
|
371
|
+
...validateExpressionStateOnly(navigateStep.url, buildPath(path, "url"), context)
|
|
372
|
+
);
|
|
373
|
+
if (navigateStep.target !== void 0 && !NAVIGATE_TARGETS.includes(navigateStep.target)) {
|
|
374
|
+
errors.push(createInvalidNavigateTargetError(navigateStep.target, path));
|
|
375
|
+
}
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
case "import": {
|
|
379
|
+
const importStep = step;
|
|
380
|
+
if (importStep.onSuccess) {
|
|
381
|
+
for (let i = 0; i < importStep.onSuccess.length; i++) {
|
|
382
|
+
const successStep = importStep.onSuccess[i];
|
|
383
|
+
if (successStep === void 0) continue;
|
|
384
|
+
errors.push(
|
|
385
|
+
...validateActionStep(successStep, buildPath(path, "onSuccess", i), context)
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (importStep.onError) {
|
|
390
|
+
for (let i = 0; i < importStep.onError.length; i++) {
|
|
391
|
+
const errorStep = importStep.onError[i];
|
|
392
|
+
if (errorStep === void 0) continue;
|
|
393
|
+
errors.push(
|
|
394
|
+
...validateActionStep(errorStep, buildPath(path, "onError", i), context)
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
case "call": {
|
|
401
|
+
const callStep = step;
|
|
402
|
+
errors.push(
|
|
403
|
+
...validateExpressionStateOnly(callStep.target, buildPath(path, "target"), context)
|
|
404
|
+
);
|
|
405
|
+
if (callStep.args) {
|
|
406
|
+
for (let i = 0; i < callStep.args.length; i++) {
|
|
407
|
+
const arg = callStep.args[i];
|
|
408
|
+
if (arg === void 0) continue;
|
|
409
|
+
errors.push(
|
|
410
|
+
...validateExpressionStateOnly(arg, buildPath(path, "args", i), context)
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (callStep.onSuccess) {
|
|
415
|
+
for (let i = 0; i < callStep.onSuccess.length; i++) {
|
|
416
|
+
const successStep = callStep.onSuccess[i];
|
|
417
|
+
if (successStep === void 0) continue;
|
|
418
|
+
errors.push(
|
|
419
|
+
...validateActionStep(successStep, buildPath(path, "onSuccess", i), context)
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
if (callStep.onError) {
|
|
424
|
+
for (let i = 0; i < callStep.onError.length; i++) {
|
|
425
|
+
const errorStep = callStep.onError[i];
|
|
426
|
+
if (errorStep === void 0) continue;
|
|
427
|
+
errors.push(
|
|
428
|
+
...validateActionStep(errorStep, buildPath(path, "onError", i), context)
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
case "subscribe": {
|
|
435
|
+
const subscribeStep = step;
|
|
436
|
+
errors.push(
|
|
437
|
+
...validateExpressionStateOnly(subscribeStep.target, buildPath(path, "target"), context)
|
|
438
|
+
);
|
|
439
|
+
if (!context.actionNames.has(subscribeStep.action)) {
|
|
440
|
+
errors.push(createUndefinedActionError(subscribeStep.action, buildPath(path, "action")));
|
|
441
|
+
}
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
case "dispose": {
|
|
445
|
+
const disposeStep = step;
|
|
446
|
+
errors.push(
|
|
447
|
+
...validateExpressionStateOnly(disposeStep.target, buildPath(path, "target"), context)
|
|
448
|
+
);
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
177
451
|
}
|
|
178
452
|
return errors;
|
|
179
453
|
}
|
|
@@ -187,6 +461,39 @@ function validateExpressionStateOnly(expr, path, context) {
|
|
|
187
461
|
break;
|
|
188
462
|
case "var":
|
|
189
463
|
break;
|
|
464
|
+
case "route": {
|
|
465
|
+
if (!hasRoute) {
|
|
466
|
+
errors.push(createRouteNotDefinedError(path));
|
|
467
|
+
} else {
|
|
468
|
+
const source = expr.source ?? "param";
|
|
469
|
+
if (source === "param" && !context.routeParams.has(expr.name)) {
|
|
470
|
+
errors.push(createUndefinedRouteParamError(expr.name, path));
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
case "import": {
|
|
476
|
+
if (!hasImports) {
|
|
477
|
+
errors.push(createImportsNotDefinedError(path));
|
|
478
|
+
} else if (!context.importNames.has(expr.name)) {
|
|
479
|
+
errors.push(createUndefinedImportError(expr.name, path));
|
|
480
|
+
}
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
case "data": {
|
|
484
|
+
if (!hasData) {
|
|
485
|
+
errors.push(createDataNotDefinedError(path));
|
|
486
|
+
} else if (!context.dataNames.has(expr.name)) {
|
|
487
|
+
errors.push(createUndefinedDataError(expr.name, path));
|
|
488
|
+
}
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
case "ref": {
|
|
492
|
+
if (!context.refNames.has(expr.name)) {
|
|
493
|
+
errors.push(createUndefinedRefError(expr.name, path));
|
|
494
|
+
}
|
|
495
|
+
break;
|
|
496
|
+
}
|
|
190
497
|
case "bin":
|
|
191
498
|
errors.push(...validateExpressionStateOnly(expr.left, buildPath(path, "left"), context));
|
|
192
499
|
errors.push(...validateExpressionStateOnly(expr.right, buildPath(path, "right"), context));
|
|
@@ -219,6 +526,39 @@ function validateExpressionInEventPayload(expr, path, context, scope) {
|
|
|
219
526
|
break;
|
|
220
527
|
case "var":
|
|
221
528
|
break;
|
|
529
|
+
case "route": {
|
|
530
|
+
if (!hasRoute) {
|
|
531
|
+
errors.push(createRouteNotDefinedError(path));
|
|
532
|
+
} else {
|
|
533
|
+
const source = expr.source ?? "param";
|
|
534
|
+
if (source === "param" && !context.routeParams.has(expr.name)) {
|
|
535
|
+
errors.push(createUndefinedRouteParamError(expr.name, path));
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
case "import": {
|
|
541
|
+
if (!hasImports) {
|
|
542
|
+
errors.push(createImportsNotDefinedError(path));
|
|
543
|
+
} else if (!context.importNames.has(expr.name)) {
|
|
544
|
+
errors.push(createUndefinedImportError(expr.name, path));
|
|
545
|
+
}
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
case "data": {
|
|
549
|
+
if (!hasData) {
|
|
550
|
+
errors.push(createDataNotDefinedError(path));
|
|
551
|
+
} else if (!context.dataNames.has(expr.name)) {
|
|
552
|
+
errors.push(createUndefinedDataError(expr.name, path));
|
|
553
|
+
}
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
case "ref": {
|
|
557
|
+
if (!context.refNames.has(expr.name)) {
|
|
558
|
+
errors.push(createUndefinedRefError(expr.name, path));
|
|
559
|
+
}
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
222
562
|
case "bin":
|
|
223
563
|
errors.push(
|
|
224
564
|
...validateExpressionInEventPayload(expr.left, buildPath(path, "left"), context, scope)
|
|
@@ -260,7 +600,7 @@ function validateExpressionInEventPayload(expr, path, context, scope) {
|
|
|
260
600
|
}
|
|
261
601
|
function validateViewNode(node, path, context, scope, options = { insideComponent: false }) {
|
|
262
602
|
const errors = [];
|
|
263
|
-
const { insideComponent, paramScope } = options;
|
|
603
|
+
const { insideComponent, insideLayout, paramScope } = options;
|
|
264
604
|
switch (node.kind) {
|
|
265
605
|
case "element":
|
|
266
606
|
if (node.props) {
|
|
@@ -343,9 +683,9 @@ function validateViewNode(node, path, context, scope, options = { insideComponen
|
|
|
343
683
|
break;
|
|
344
684
|
}
|
|
345
685
|
case "slot":
|
|
346
|
-
if (!insideComponent) {
|
|
686
|
+
if (!insideComponent && !insideLayout) {
|
|
347
687
|
errors.push(
|
|
348
|
-
createSchemaError(`Slot can only be used inside component definitions`, path)
|
|
688
|
+
createSchemaError(`Slot can only be used inside component definitions or layouts`, path)
|
|
349
689
|
);
|
|
350
690
|
}
|
|
351
691
|
break;
|
|
@@ -372,6 +712,26 @@ function validateComponentProps(node, componentDef, path, context, scope, paramS
|
|
|
372
712
|
return errors;
|
|
373
713
|
}
|
|
374
714
|
var ast;
|
|
715
|
+
var hasRoute;
|
|
716
|
+
var hasImports;
|
|
717
|
+
var hasData;
|
|
718
|
+
function validateRouteDefinition(route, context) {
|
|
719
|
+
const errors = [];
|
|
720
|
+
const emptyScope = /* @__PURE__ */ new Set();
|
|
721
|
+
if (route.title) {
|
|
722
|
+
errors.push(
|
|
723
|
+
...validateExpression(route.title, "/route/title", context, emptyScope)
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
if (route.meta) {
|
|
727
|
+
for (const [key, value] of Object.entries(route.meta)) {
|
|
728
|
+
errors.push(
|
|
729
|
+
...validateExpression(value, `/route/meta/${key}`, context, emptyScope)
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
return errors;
|
|
734
|
+
}
|
|
375
735
|
function collectComponentCalls(node) {
|
|
376
736
|
const calls = /* @__PURE__ */ new Set();
|
|
377
737
|
switch (node.kind) {
|
|
@@ -472,6 +832,18 @@ function validateComponents(programAst, context) {
|
|
|
472
832
|
}
|
|
473
833
|
return errors;
|
|
474
834
|
}
|
|
835
|
+
function validateLifecycleHooks(lifecycle, context) {
|
|
836
|
+
const errors = [];
|
|
837
|
+
if (!lifecycle) return errors;
|
|
838
|
+
const hooks = ["onMount", "onUnmount", "onRouteEnter", "onRouteLeave"];
|
|
839
|
+
for (const hook of hooks) {
|
|
840
|
+
const actionName = lifecycle[hook];
|
|
841
|
+
if (actionName && !context.actionNames.has(actionName)) {
|
|
842
|
+
errors.push(createUndefinedActionError(actionName, `/lifecycle/${hook}`));
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
return errors;
|
|
846
|
+
}
|
|
475
847
|
function validateActions(programAst, context) {
|
|
476
848
|
const errors = [];
|
|
477
849
|
for (let i = 0; i < programAst.actions.length; i++) {
|
|
@@ -487,17 +859,81 @@ function validateActions(programAst, context) {
|
|
|
487
859
|
}
|
|
488
860
|
return errors;
|
|
489
861
|
}
|
|
862
|
+
function validateDataSources(programAst, context) {
|
|
863
|
+
const errors = [];
|
|
864
|
+
if (!programAst.data) return errors;
|
|
865
|
+
for (const [name, source] of Object.entries(programAst.data)) {
|
|
866
|
+
const path = `/data/${name}`;
|
|
867
|
+
if (!DATA_SOURCE_TYPES.includes(source.type)) {
|
|
868
|
+
errors.push(createInvalidDataSourceError(name, `invalid type '${source.type}'`, path));
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
if (source.transform !== void 0) {
|
|
872
|
+
if (!DATA_TRANSFORMS.includes(source.transform)) {
|
|
873
|
+
errors.push(createInvalidDataSourceError(name, `invalid transform '${source.transform}'`, path));
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
switch (source.type) {
|
|
877
|
+
case "glob":
|
|
878
|
+
if (typeof source.pattern !== "string") {
|
|
879
|
+
errors.push(createInvalidDataSourceError(name, `glob type requires 'pattern' field`, path));
|
|
880
|
+
}
|
|
881
|
+
break;
|
|
882
|
+
case "file":
|
|
883
|
+
if (typeof source.path !== "string") {
|
|
884
|
+
errors.push(createInvalidDataSourceError(name, `file type requires 'path' field`, path));
|
|
885
|
+
}
|
|
886
|
+
break;
|
|
887
|
+
case "api":
|
|
888
|
+
if (typeof source.url !== "string") {
|
|
889
|
+
errors.push(createInvalidDataSourceError(name, `api type requires 'url' field`, path));
|
|
890
|
+
}
|
|
891
|
+
break;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return errors;
|
|
895
|
+
}
|
|
896
|
+
function validateGetStaticPaths(programAst, context) {
|
|
897
|
+
const errors = [];
|
|
898
|
+
const getStaticPaths = programAst.route?.getStaticPaths;
|
|
899
|
+
if (!getStaticPaths) return errors;
|
|
900
|
+
const path = "/route/getStaticPaths";
|
|
901
|
+
if (!programAst.data) {
|
|
902
|
+
errors.push(createDataNotDefinedError(path));
|
|
903
|
+
return errors;
|
|
904
|
+
}
|
|
905
|
+
if (!context.dataNames.has(getStaticPaths.source)) {
|
|
906
|
+
errors.push(createUndefinedDataSourceError(getStaticPaths.source, path));
|
|
907
|
+
}
|
|
908
|
+
for (const [paramName, paramExpr] of Object.entries(getStaticPaths.params)) {
|
|
909
|
+
errors.push(
|
|
910
|
+
...validateExpressionStateOnly(paramExpr, `${path}/params/${paramName}`, context)
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
return errors;
|
|
914
|
+
}
|
|
490
915
|
function analyzePass(programAst) {
|
|
491
916
|
ast = programAst;
|
|
917
|
+
hasRoute = !!programAst.route;
|
|
918
|
+
hasImports = !!programAst.imports;
|
|
919
|
+
hasData = !!programAst.data;
|
|
920
|
+
const isLayout = programAst.type === "layout";
|
|
492
921
|
const context = collectContext(programAst);
|
|
493
922
|
const errors = [];
|
|
494
923
|
errors.push(...checkDuplicateActions(programAst));
|
|
924
|
+
errors.push(...validateDataSources(programAst, context));
|
|
925
|
+
errors.push(...validateGetStaticPaths(programAst, context));
|
|
495
926
|
errors.push(...validateActions(programAst, context));
|
|
927
|
+
errors.push(...validateLifecycleHooks(programAst.lifecycle, context));
|
|
496
928
|
errors.push(...detectComponentCycles(programAst, context));
|
|
497
929
|
errors.push(...validateComponents(programAst, context));
|
|
930
|
+
if (programAst.route) {
|
|
931
|
+
errors.push(...validateRouteDefinition(programAst.route, context));
|
|
932
|
+
}
|
|
498
933
|
errors.push(
|
|
499
934
|
...validateViewNode(programAst.view, "/view", context, /* @__PURE__ */ new Set(), {
|
|
500
|
-
insideComponent: false
|
|
935
|
+
insideComponent: false,
|
|
936
|
+
insideLayout: isLayout
|
|
501
937
|
})
|
|
502
938
|
);
|
|
503
939
|
if (errors.length > 0) {
|
|
@@ -588,6 +1024,34 @@ function transformExpression(expr, ctx) {
|
|
|
588
1024
|
base: transformExpression(expr.base, ctx),
|
|
589
1025
|
path: expr.path
|
|
590
1026
|
};
|
|
1027
|
+
case "route":
|
|
1028
|
+
return {
|
|
1029
|
+
expr: "route",
|
|
1030
|
+
name: expr.name,
|
|
1031
|
+
source: expr.source ?? "param"
|
|
1032
|
+
};
|
|
1033
|
+
case "import": {
|
|
1034
|
+
const importExpr = {
|
|
1035
|
+
expr: "import",
|
|
1036
|
+
name: expr.name
|
|
1037
|
+
};
|
|
1038
|
+
if (expr.path) {
|
|
1039
|
+
importExpr.path = expr.path;
|
|
1040
|
+
}
|
|
1041
|
+
return importExpr;
|
|
1042
|
+
}
|
|
1043
|
+
case "data": {
|
|
1044
|
+
const dataExpr = {
|
|
1045
|
+
expr: "import",
|
|
1046
|
+
name: expr.name
|
|
1047
|
+
};
|
|
1048
|
+
if (expr.path) {
|
|
1049
|
+
dataExpr.path = expr.path;
|
|
1050
|
+
}
|
|
1051
|
+
return dataExpr;
|
|
1052
|
+
}
|
|
1053
|
+
case "ref":
|
|
1054
|
+
return { expr: "ref", name: expr.name };
|
|
591
1055
|
}
|
|
592
1056
|
}
|
|
593
1057
|
function transformEventHandler(handler, ctx) {
|
|
@@ -648,6 +1112,113 @@ function transformActionStep(step) {
|
|
|
648
1112
|
}
|
|
649
1113
|
return fetchStep;
|
|
650
1114
|
}
|
|
1115
|
+
case "storage": {
|
|
1116
|
+
const storageStep = step;
|
|
1117
|
+
const compiledStorageStep = {
|
|
1118
|
+
do: "storage",
|
|
1119
|
+
operation: storageStep.operation,
|
|
1120
|
+
key: transformExpression(storageStep.key, emptyContext),
|
|
1121
|
+
storage: storageStep.storage
|
|
1122
|
+
};
|
|
1123
|
+
if (storageStep.value) {
|
|
1124
|
+
compiledStorageStep.value = transformExpression(storageStep.value, emptyContext);
|
|
1125
|
+
}
|
|
1126
|
+
if (storageStep.result) {
|
|
1127
|
+
compiledStorageStep.result = storageStep.result;
|
|
1128
|
+
}
|
|
1129
|
+
if (storageStep.onSuccess) {
|
|
1130
|
+
compiledStorageStep.onSuccess = storageStep.onSuccess.map(transformActionStep);
|
|
1131
|
+
}
|
|
1132
|
+
if (storageStep.onError) {
|
|
1133
|
+
compiledStorageStep.onError = storageStep.onError.map(transformActionStep);
|
|
1134
|
+
}
|
|
1135
|
+
return compiledStorageStep;
|
|
1136
|
+
}
|
|
1137
|
+
case "clipboard": {
|
|
1138
|
+
const clipboardStep = step;
|
|
1139
|
+
const compiledClipboardStep = {
|
|
1140
|
+
do: "clipboard",
|
|
1141
|
+
operation: clipboardStep.operation
|
|
1142
|
+
};
|
|
1143
|
+
if (clipboardStep.value) {
|
|
1144
|
+
compiledClipboardStep.value = transformExpression(clipboardStep.value, emptyContext);
|
|
1145
|
+
}
|
|
1146
|
+
if (clipboardStep.result) {
|
|
1147
|
+
compiledClipboardStep.result = clipboardStep.result;
|
|
1148
|
+
}
|
|
1149
|
+
if (clipboardStep.onSuccess) {
|
|
1150
|
+
compiledClipboardStep.onSuccess = clipboardStep.onSuccess.map(transformActionStep);
|
|
1151
|
+
}
|
|
1152
|
+
if (clipboardStep.onError) {
|
|
1153
|
+
compiledClipboardStep.onError = clipboardStep.onError.map(transformActionStep);
|
|
1154
|
+
}
|
|
1155
|
+
return compiledClipboardStep;
|
|
1156
|
+
}
|
|
1157
|
+
case "navigate": {
|
|
1158
|
+
const navigateStep = step;
|
|
1159
|
+
const compiledNavigateStep = {
|
|
1160
|
+
do: "navigate",
|
|
1161
|
+
url: transformExpression(navigateStep.url, emptyContext)
|
|
1162
|
+
};
|
|
1163
|
+
if (navigateStep.target) {
|
|
1164
|
+
compiledNavigateStep.target = navigateStep.target;
|
|
1165
|
+
}
|
|
1166
|
+
if (navigateStep.replace !== void 0) {
|
|
1167
|
+
compiledNavigateStep.replace = navigateStep.replace;
|
|
1168
|
+
}
|
|
1169
|
+
return compiledNavigateStep;
|
|
1170
|
+
}
|
|
1171
|
+
case "import": {
|
|
1172
|
+
const importStep = step;
|
|
1173
|
+
const compiledImportStep = {
|
|
1174
|
+
do: "import",
|
|
1175
|
+
module: importStep.module,
|
|
1176
|
+
result: importStep.result
|
|
1177
|
+
};
|
|
1178
|
+
if (importStep.onSuccess) {
|
|
1179
|
+
compiledImportStep.onSuccess = importStep.onSuccess.map(transformActionStep);
|
|
1180
|
+
}
|
|
1181
|
+
if (importStep.onError) {
|
|
1182
|
+
compiledImportStep.onError = importStep.onError.map(transformActionStep);
|
|
1183
|
+
}
|
|
1184
|
+
return compiledImportStep;
|
|
1185
|
+
}
|
|
1186
|
+
case "call": {
|
|
1187
|
+
const callStep = step;
|
|
1188
|
+
const compiledCallStep = {
|
|
1189
|
+
do: "call",
|
|
1190
|
+
target: transformExpression(callStep.target, emptyContext)
|
|
1191
|
+
};
|
|
1192
|
+
if (callStep.args) {
|
|
1193
|
+
compiledCallStep.args = callStep.args.map((arg) => transformExpression(arg, emptyContext));
|
|
1194
|
+
}
|
|
1195
|
+
if (callStep.result) {
|
|
1196
|
+
compiledCallStep.result = callStep.result;
|
|
1197
|
+
}
|
|
1198
|
+
if (callStep.onSuccess) {
|
|
1199
|
+
compiledCallStep.onSuccess = callStep.onSuccess.map(transformActionStep);
|
|
1200
|
+
}
|
|
1201
|
+
if (callStep.onError) {
|
|
1202
|
+
compiledCallStep.onError = callStep.onError.map(transformActionStep);
|
|
1203
|
+
}
|
|
1204
|
+
return compiledCallStep;
|
|
1205
|
+
}
|
|
1206
|
+
case "subscribe": {
|
|
1207
|
+
const subscribeStep = step;
|
|
1208
|
+
return {
|
|
1209
|
+
do: "subscribe",
|
|
1210
|
+
target: transformExpression(subscribeStep.target, emptyContext),
|
|
1211
|
+
event: subscribeStep.event,
|
|
1212
|
+
action: subscribeStep.action
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
case "dispose": {
|
|
1216
|
+
const disposeStep = step;
|
|
1217
|
+
return {
|
|
1218
|
+
do: "dispose",
|
|
1219
|
+
target: transformExpression(disposeStep.target, emptyContext)
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
651
1222
|
}
|
|
652
1223
|
}
|
|
653
1224
|
function flattenSlotChildren(children, ctx) {
|
|
@@ -670,6 +1241,9 @@ function transformViewNode(node, ctx) {
|
|
|
670
1241
|
kind: "element",
|
|
671
1242
|
tag: node.tag
|
|
672
1243
|
};
|
|
1244
|
+
if (node.ref) {
|
|
1245
|
+
compiledElement.ref = node.ref;
|
|
1246
|
+
}
|
|
673
1247
|
if (node.props) {
|
|
674
1248
|
compiledElement.props = {};
|
|
675
1249
|
for (const [propName, propValue] of Object.entries(node.props)) {
|
|
@@ -790,16 +1364,75 @@ function transformActions(actions) {
|
|
|
790
1364
|
}
|
|
791
1365
|
return compiledActions;
|
|
792
1366
|
}
|
|
793
|
-
function
|
|
1367
|
+
function extractRouteParams2(path) {
|
|
1368
|
+
const params = [];
|
|
1369
|
+
const segments = path.split("/");
|
|
1370
|
+
for (const segment of segments) {
|
|
1371
|
+
if (segment.startsWith(":")) {
|
|
1372
|
+
params.push(segment.slice(1));
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
return params;
|
|
1376
|
+
}
|
|
1377
|
+
function transformRouteDefinition(route, ctx) {
|
|
1378
|
+
const compiled = {
|
|
1379
|
+
path: route.path,
|
|
1380
|
+
params: extractRouteParams2(route.path)
|
|
1381
|
+
};
|
|
1382
|
+
if (route.title) {
|
|
1383
|
+
compiled.title = transformExpression(route.title, ctx);
|
|
1384
|
+
}
|
|
1385
|
+
if (route.layout) {
|
|
1386
|
+
compiled.layout = route.layout;
|
|
1387
|
+
}
|
|
1388
|
+
if (route.meta) {
|
|
1389
|
+
compiled.meta = {};
|
|
1390
|
+
for (const [key, value] of Object.entries(route.meta)) {
|
|
1391
|
+
compiled.meta[key] = transformExpression(value, ctx);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
return compiled;
|
|
1395
|
+
}
|
|
1396
|
+
function transformLifecycleHooks(lifecycle) {
|
|
1397
|
+
if (!lifecycle) return void 0;
|
|
1398
|
+
const hasAnyHook = lifecycle.onMount || lifecycle.onUnmount || lifecycle.onRouteEnter || lifecycle.onRouteLeave;
|
|
1399
|
+
if (!hasAnyHook) return void 0;
|
|
1400
|
+
const result = {};
|
|
1401
|
+
if (lifecycle.onMount) {
|
|
1402
|
+
result.onMount = lifecycle.onMount;
|
|
1403
|
+
}
|
|
1404
|
+
if (lifecycle.onUnmount) {
|
|
1405
|
+
result.onUnmount = lifecycle.onUnmount;
|
|
1406
|
+
}
|
|
1407
|
+
if (lifecycle.onRouteEnter) {
|
|
1408
|
+
result.onRouteEnter = lifecycle.onRouteEnter;
|
|
1409
|
+
}
|
|
1410
|
+
if (lifecycle.onRouteLeave) {
|
|
1411
|
+
result.onRouteLeave = lifecycle.onRouteLeave;
|
|
1412
|
+
}
|
|
1413
|
+
return result;
|
|
1414
|
+
}
|
|
1415
|
+
function transformPass(ast2, _context, importData) {
|
|
794
1416
|
const ctx = {
|
|
795
1417
|
components: ast2.components || {}
|
|
796
1418
|
};
|
|
797
|
-
|
|
1419
|
+
const result = {
|
|
798
1420
|
version: "1.0",
|
|
799
1421
|
state: transformState(ast2.state),
|
|
800
1422
|
actions: transformActions(ast2.actions),
|
|
801
1423
|
view: transformViewNode(ast2.view, ctx)
|
|
802
1424
|
};
|
|
1425
|
+
if (ast2.route) {
|
|
1426
|
+
result.route = transformRouteDefinition(ast2.route, ctx);
|
|
1427
|
+
}
|
|
1428
|
+
const lifecycle = transformLifecycleHooks(ast2.lifecycle);
|
|
1429
|
+
if (lifecycle) {
|
|
1430
|
+
result.lifecycle = lifecycle;
|
|
1431
|
+
}
|
|
1432
|
+
if (importData && Object.keys(importData).length > 0) {
|
|
1433
|
+
result.importData = importData;
|
|
1434
|
+
}
|
|
1435
|
+
return result;
|
|
803
1436
|
}
|
|
804
1437
|
|
|
805
1438
|
// src/compile.ts
|
|
@@ -827,10 +1460,452 @@ function compile(input) {
|
|
|
827
1460
|
|
|
828
1461
|
// src/index.ts
|
|
829
1462
|
import { createUndefinedVarError as createUndefinedVarError2 } from "@constela/core";
|
|
1463
|
+
|
|
1464
|
+
// src/passes/analyze-layout.ts
|
|
1465
|
+
import {
|
|
1466
|
+
createLayoutMissingSlotError,
|
|
1467
|
+
createDuplicateSlotNameError,
|
|
1468
|
+
createDuplicateDefaultSlotError,
|
|
1469
|
+
createSlotInLoopError,
|
|
1470
|
+
createUndefinedStateError as createUndefinedStateError2,
|
|
1471
|
+
createUndefinedActionError as createUndefinedActionError2,
|
|
1472
|
+
isEventHandler as isEventHandler3
|
|
1473
|
+
} from "@constela/core";
|
|
1474
|
+
function buildPath2(base, ...segments) {
|
|
1475
|
+
return segments.reduce((p, s) => `${p}/${s}`, base);
|
|
1476
|
+
}
|
|
1477
|
+
function collectContext2(layout) {
|
|
1478
|
+
const stateNames = new Set(layout.state ? Object.keys(layout.state) : []);
|
|
1479
|
+
const actionNames = new Set(layout.actions ? layout.actions.map((a) => a.name) : []);
|
|
1480
|
+
const componentNames = new Set(
|
|
1481
|
+
layout.components ? Object.keys(layout.components) : []
|
|
1482
|
+
);
|
|
1483
|
+
return { stateNames, actionNames, componentNames, routeParams: /* @__PURE__ */ new Set(), importNames: /* @__PURE__ */ new Set() };
|
|
1484
|
+
}
|
|
1485
|
+
function findSlotNodes(node, path, slots, inLoop = false) {
|
|
1486
|
+
if (node.kind === "slot") {
|
|
1487
|
+
slots.push({
|
|
1488
|
+
name: node.name,
|
|
1489
|
+
path,
|
|
1490
|
+
inLoop
|
|
1491
|
+
});
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
if (node.kind === "element" && node.children) {
|
|
1495
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
1496
|
+
const child = node.children[i];
|
|
1497
|
+
if (child) {
|
|
1498
|
+
findSlotNodes(child, buildPath2(path, "children", i), slots, inLoop);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
if (node.kind === "if") {
|
|
1503
|
+
findSlotNodes(node.then, buildPath2(path, "then"), slots, inLoop);
|
|
1504
|
+
if (node.else) {
|
|
1505
|
+
findSlotNodes(node.else, buildPath2(path, "else"), slots, inLoop);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
if (node.kind === "each") {
|
|
1509
|
+
findSlotNodes(node.body, buildPath2(path, "body"), slots, true);
|
|
1510
|
+
}
|
|
1511
|
+
if (node.kind === "component" && node.children) {
|
|
1512
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
1513
|
+
const child = node.children[i];
|
|
1514
|
+
if (child) {
|
|
1515
|
+
findSlotNodes(child, buildPath2(path, "children", i), slots, inLoop);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
function validateSlots(layout) {
|
|
1521
|
+
const errors = [];
|
|
1522
|
+
const slots = [];
|
|
1523
|
+
const slotNames = /* @__PURE__ */ new Set();
|
|
1524
|
+
let hasDefaultSlot = false;
|
|
1525
|
+
findSlotNodes(layout.view, "/view", slots);
|
|
1526
|
+
if (slots.length === 0) {
|
|
1527
|
+
errors.push(createLayoutMissingSlotError("/view"));
|
|
1528
|
+
return { errors, slotNames, hasDefaultSlot };
|
|
1529
|
+
}
|
|
1530
|
+
const seenNames = /* @__PURE__ */ new Map();
|
|
1531
|
+
let defaultSlotPath;
|
|
1532
|
+
for (const slot of slots) {
|
|
1533
|
+
if (slot.inLoop) {
|
|
1534
|
+
errors.push(createSlotInLoopError(slot.path));
|
|
1535
|
+
continue;
|
|
1536
|
+
}
|
|
1537
|
+
if (slot.name === void 0 || slot.name === "") {
|
|
1538
|
+
if (defaultSlotPath !== void 0) {
|
|
1539
|
+
errors.push(createDuplicateDefaultSlotError(slot.path));
|
|
1540
|
+
} else {
|
|
1541
|
+
defaultSlotPath = slot.path;
|
|
1542
|
+
hasDefaultSlot = true;
|
|
1543
|
+
}
|
|
1544
|
+
} else {
|
|
1545
|
+
const existingPath = seenNames.get(slot.name);
|
|
1546
|
+
if (existingPath !== void 0) {
|
|
1547
|
+
errors.push(createDuplicateSlotNameError(slot.name, slot.path));
|
|
1548
|
+
} else {
|
|
1549
|
+
seenNames.set(slot.name, slot.path);
|
|
1550
|
+
slotNames.add(slot.name);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
return { errors, slotNames, hasDefaultSlot };
|
|
1555
|
+
}
|
|
1556
|
+
function validateExpression2(expr, path, stateNames) {
|
|
1557
|
+
const errors = [];
|
|
1558
|
+
switch (expr.expr) {
|
|
1559
|
+
case "state":
|
|
1560
|
+
if (!stateNames.has(expr.name)) {
|
|
1561
|
+
errors.push(createUndefinedStateError2(expr.name, path));
|
|
1562
|
+
}
|
|
1563
|
+
break;
|
|
1564
|
+
case "bin":
|
|
1565
|
+
errors.push(...validateExpression2(expr.left, buildPath2(path, "left"), stateNames));
|
|
1566
|
+
errors.push(...validateExpression2(expr.right, buildPath2(path, "right"), stateNames));
|
|
1567
|
+
break;
|
|
1568
|
+
case "not":
|
|
1569
|
+
errors.push(...validateExpression2(expr.operand, buildPath2(path, "operand"), stateNames));
|
|
1570
|
+
break;
|
|
1571
|
+
case "cond":
|
|
1572
|
+
errors.push(...validateExpression2(expr.if, buildPath2(path, "if"), stateNames));
|
|
1573
|
+
errors.push(...validateExpression2(expr.then, buildPath2(path, "then"), stateNames));
|
|
1574
|
+
errors.push(...validateExpression2(expr.else, buildPath2(path, "else"), stateNames));
|
|
1575
|
+
break;
|
|
1576
|
+
case "get":
|
|
1577
|
+
errors.push(...validateExpression2(expr.base, buildPath2(path, "base"), stateNames));
|
|
1578
|
+
break;
|
|
1579
|
+
case "lit":
|
|
1580
|
+
case "var":
|
|
1581
|
+
case "param":
|
|
1582
|
+
case "route":
|
|
1583
|
+
case "import":
|
|
1584
|
+
break;
|
|
1585
|
+
}
|
|
1586
|
+
return errors;
|
|
1587
|
+
}
|
|
1588
|
+
function validateViewNode2(node, path, stateNames, actionNames) {
|
|
1589
|
+
const errors = [];
|
|
1590
|
+
switch (node.kind) {
|
|
1591
|
+
case "element":
|
|
1592
|
+
if (node.props) {
|
|
1593
|
+
for (const [propName, propValue] of Object.entries(node.props)) {
|
|
1594
|
+
const propPath = buildPath2(path, "props", propName);
|
|
1595
|
+
if (isEventHandler3(propValue)) {
|
|
1596
|
+
if (!actionNames.has(propValue.action)) {
|
|
1597
|
+
errors.push(createUndefinedActionError2(propValue.action, propPath));
|
|
1598
|
+
}
|
|
1599
|
+
if (propValue.payload) {
|
|
1600
|
+
errors.push(
|
|
1601
|
+
...validateExpression2(propValue.payload, buildPath2(propPath, "payload"), stateNames)
|
|
1602
|
+
);
|
|
1603
|
+
}
|
|
1604
|
+
} else {
|
|
1605
|
+
errors.push(...validateExpression2(propValue, propPath, stateNames));
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
if (node.children) {
|
|
1610
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
1611
|
+
const child = node.children[i];
|
|
1612
|
+
if (child) {
|
|
1613
|
+
errors.push(
|
|
1614
|
+
...validateViewNode2(child, buildPath2(path, "children", i), stateNames, actionNames)
|
|
1615
|
+
);
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
break;
|
|
1620
|
+
case "text":
|
|
1621
|
+
errors.push(...validateExpression2(node.value, buildPath2(path, "value"), stateNames));
|
|
1622
|
+
break;
|
|
1623
|
+
case "if":
|
|
1624
|
+
errors.push(
|
|
1625
|
+
...validateExpression2(node.condition, buildPath2(path, "condition"), stateNames)
|
|
1626
|
+
);
|
|
1627
|
+
errors.push(...validateViewNode2(node.then, buildPath2(path, "then"), stateNames, actionNames));
|
|
1628
|
+
if (node.else) {
|
|
1629
|
+
errors.push(...validateViewNode2(node.else, buildPath2(path, "else"), stateNames, actionNames));
|
|
1630
|
+
}
|
|
1631
|
+
break;
|
|
1632
|
+
case "each":
|
|
1633
|
+
errors.push(...validateExpression2(node.items, buildPath2(path, "items"), stateNames));
|
|
1634
|
+
errors.push(...validateViewNode2(node.body, buildPath2(path, "body"), stateNames, actionNames));
|
|
1635
|
+
break;
|
|
1636
|
+
case "slot":
|
|
1637
|
+
break;
|
|
1638
|
+
}
|
|
1639
|
+
return errors;
|
|
1640
|
+
}
|
|
1641
|
+
function analyzeLayoutPass(layout) {
|
|
1642
|
+
const baseContext = collectContext2(layout);
|
|
1643
|
+
const errors = [];
|
|
1644
|
+
const { errors: slotErrors, slotNames, hasDefaultSlot } = validateSlots(layout);
|
|
1645
|
+
errors.push(...slotErrors);
|
|
1646
|
+
if (slotErrors.length > 0) {
|
|
1647
|
+
return {
|
|
1648
|
+
ok: false,
|
|
1649
|
+
errors
|
|
1650
|
+
};
|
|
1651
|
+
}
|
|
1652
|
+
errors.push(
|
|
1653
|
+
...validateViewNode2(
|
|
1654
|
+
layout.view,
|
|
1655
|
+
"/view",
|
|
1656
|
+
baseContext.stateNames,
|
|
1657
|
+
baseContext.actionNames
|
|
1658
|
+
)
|
|
1659
|
+
);
|
|
1660
|
+
if (errors.length > 0) {
|
|
1661
|
+
return {
|
|
1662
|
+
ok: false,
|
|
1663
|
+
errors
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
return {
|
|
1667
|
+
ok: true,
|
|
1668
|
+
context: {
|
|
1669
|
+
...baseContext,
|
|
1670
|
+
slotNames,
|
|
1671
|
+
hasDefaultSlot
|
|
1672
|
+
}
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
// src/passes/transform-layout.ts
|
|
1677
|
+
function transformState2(state) {
|
|
1678
|
+
if (!state) return {};
|
|
1679
|
+
const result = {};
|
|
1680
|
+
for (const [name, field] of Object.entries(state)) {
|
|
1681
|
+
result[name] = {
|
|
1682
|
+
type: field.type,
|
|
1683
|
+
initial: field.initial
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
return result;
|
|
1687
|
+
}
|
|
1688
|
+
function transformActions2(actions) {
|
|
1689
|
+
if (!actions) return [];
|
|
1690
|
+
return actions.map((action) => ({
|
|
1691
|
+
name: action.name,
|
|
1692
|
+
steps: action.steps.map((step) => {
|
|
1693
|
+
if (step.do === "set") {
|
|
1694
|
+
return {
|
|
1695
|
+
do: "set",
|
|
1696
|
+
target: step.target,
|
|
1697
|
+
value: { expr: "lit", value: null }
|
|
1698
|
+
// Simplified for now
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1701
|
+
if (step.do === "update") {
|
|
1702
|
+
return {
|
|
1703
|
+
do: "update",
|
|
1704
|
+
target: step.target,
|
|
1705
|
+
operation: step.operation
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
return {
|
|
1709
|
+
do: "fetch",
|
|
1710
|
+
url: { expr: "lit", value: "" }
|
|
1711
|
+
};
|
|
1712
|
+
})
|
|
1713
|
+
}));
|
|
1714
|
+
}
|
|
1715
|
+
function transformViewNode2(node, ctx) {
|
|
1716
|
+
switch (node.kind) {
|
|
1717
|
+
case "element": {
|
|
1718
|
+
const result = {
|
|
1719
|
+
kind: "element",
|
|
1720
|
+
tag: node.tag
|
|
1721
|
+
};
|
|
1722
|
+
if (node.props) {
|
|
1723
|
+
result.props = {};
|
|
1724
|
+
for (const [key, value] of Object.entries(node.props)) {
|
|
1725
|
+
if ("event" in value) {
|
|
1726
|
+
result.props[key] = {
|
|
1727
|
+
event: value.event,
|
|
1728
|
+
action: value.action
|
|
1729
|
+
};
|
|
1730
|
+
} else {
|
|
1731
|
+
result.props[key] = value;
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
if (node.children && node.children.length > 0) {
|
|
1736
|
+
result.children = node.children.map(
|
|
1737
|
+
(child) => transformViewNode2(child, ctx)
|
|
1738
|
+
);
|
|
1739
|
+
}
|
|
1740
|
+
return result;
|
|
1741
|
+
}
|
|
1742
|
+
case "text":
|
|
1743
|
+
return {
|
|
1744
|
+
kind: "text",
|
|
1745
|
+
value: node.value
|
|
1746
|
+
};
|
|
1747
|
+
case "if": {
|
|
1748
|
+
const result = {
|
|
1749
|
+
kind: "if",
|
|
1750
|
+
condition: node.condition,
|
|
1751
|
+
then: transformViewNode2(node.then, ctx)
|
|
1752
|
+
};
|
|
1753
|
+
if (node.else) {
|
|
1754
|
+
result.else = transformViewNode2(node.else, ctx);
|
|
1755
|
+
}
|
|
1756
|
+
return result;
|
|
1757
|
+
}
|
|
1758
|
+
case "each":
|
|
1759
|
+
return {
|
|
1760
|
+
kind: "each",
|
|
1761
|
+
items: node.items,
|
|
1762
|
+
as: node.as,
|
|
1763
|
+
body: transformViewNode2(node.body, ctx)
|
|
1764
|
+
};
|
|
1765
|
+
case "slot":
|
|
1766
|
+
return {
|
|
1767
|
+
kind: "slot",
|
|
1768
|
+
name: node.name
|
|
1769
|
+
};
|
|
1770
|
+
case "component": {
|
|
1771
|
+
const def = ctx.components[node.name];
|
|
1772
|
+
if (def) {
|
|
1773
|
+
return transformViewNode2(def.view, ctx);
|
|
1774
|
+
}
|
|
1775
|
+
return { kind: "element", tag: "div" };
|
|
1776
|
+
}
|
|
1777
|
+
case "markdown":
|
|
1778
|
+
return {
|
|
1779
|
+
kind: "markdown",
|
|
1780
|
+
content: node.content
|
|
1781
|
+
};
|
|
1782
|
+
case "code":
|
|
1783
|
+
return {
|
|
1784
|
+
kind: "code",
|
|
1785
|
+
language: node.language,
|
|
1786
|
+
content: node.content
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
function transformLayoutPass(layout, _context) {
|
|
1791
|
+
const ctx = {
|
|
1792
|
+
components: layout.components || {}
|
|
1793
|
+
};
|
|
1794
|
+
return {
|
|
1795
|
+
version: "1.0",
|
|
1796
|
+
type: "layout",
|
|
1797
|
+
state: transformState2(layout.state),
|
|
1798
|
+
actions: transformActions2(layout.actions),
|
|
1799
|
+
view: transformViewNode2(layout.view, ctx),
|
|
1800
|
+
components: layout.components
|
|
1801
|
+
};
|
|
1802
|
+
}
|
|
1803
|
+
function deepCloneNode(node) {
|
|
1804
|
+
return JSON.parse(JSON.stringify(node));
|
|
1805
|
+
}
|
|
1806
|
+
function replaceSlots(node, defaultContent, namedContent) {
|
|
1807
|
+
if (node.kind === "slot") {
|
|
1808
|
+
const slotName = node.name;
|
|
1809
|
+
if (slotName && namedContent?.[slotName]) {
|
|
1810
|
+
return deepCloneNode(namedContent[slotName]);
|
|
1811
|
+
}
|
|
1812
|
+
return deepCloneNode(defaultContent);
|
|
1813
|
+
}
|
|
1814
|
+
if (node.kind === "element") {
|
|
1815
|
+
const children = node.children;
|
|
1816
|
+
if (children && children.length > 0) {
|
|
1817
|
+
const newChildren = children.map((child) => replaceSlots(child, defaultContent, namedContent));
|
|
1818
|
+
return {
|
|
1819
|
+
...node,
|
|
1820
|
+
children: newChildren
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
return node;
|
|
1824
|
+
}
|
|
1825
|
+
if (node.kind === "if") {
|
|
1826
|
+
const ifNode = node;
|
|
1827
|
+
const result = {
|
|
1828
|
+
...node,
|
|
1829
|
+
then: replaceSlots(ifNode.then, defaultContent, namedContent)
|
|
1830
|
+
};
|
|
1831
|
+
if (ifNode.else) {
|
|
1832
|
+
result.else = replaceSlots(ifNode.else, defaultContent, namedContent);
|
|
1833
|
+
}
|
|
1834
|
+
return result;
|
|
1835
|
+
}
|
|
1836
|
+
if (node.kind === "each") {
|
|
1837
|
+
const eachNode = node;
|
|
1838
|
+
return {
|
|
1839
|
+
...node,
|
|
1840
|
+
body: replaceSlots(eachNode.body, defaultContent, namedContent)
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
return node;
|
|
1844
|
+
}
|
|
1845
|
+
function composeLayoutWithPage(layout, page, slots) {
|
|
1846
|
+
const layoutView = deepCloneNode(layout.view);
|
|
1847
|
+
const namedContent = slots ? Object.fromEntries(
|
|
1848
|
+
Object.entries(slots).map(([name, node]) => [name, node])
|
|
1849
|
+
) : void 0;
|
|
1850
|
+
const composedView = replaceSlots(layoutView, page.view, namedContent);
|
|
1851
|
+
const mergedState = {};
|
|
1852
|
+
for (const [name, field] of Object.entries(page.state)) {
|
|
1853
|
+
mergedState[name] = field;
|
|
1854
|
+
}
|
|
1855
|
+
for (const [name, field] of Object.entries(layout.state)) {
|
|
1856
|
+
if (name in mergedState) {
|
|
1857
|
+
mergedState[`$layout.${name}`] = field;
|
|
1858
|
+
} else {
|
|
1859
|
+
mergedState[name] = field;
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
const getActionsArray = (actions) => {
|
|
1863
|
+
if (Array.isArray(actions)) return actions;
|
|
1864
|
+
if (typeof actions === "object" && actions !== null) {
|
|
1865
|
+
return Object.values(actions);
|
|
1866
|
+
}
|
|
1867
|
+
return [];
|
|
1868
|
+
};
|
|
1869
|
+
const pageActions = getActionsArray(page.actions);
|
|
1870
|
+
const layoutActions = getActionsArray(layout.actions);
|
|
1871
|
+
const pageActionNames = new Set(pageActions.map((a) => a.name));
|
|
1872
|
+
const mergedActions = {};
|
|
1873
|
+
for (const action of pageActions) {
|
|
1874
|
+
mergedActions[action.name] = action;
|
|
1875
|
+
}
|
|
1876
|
+
for (const action of layoutActions) {
|
|
1877
|
+
if (pageActionNames.has(action.name)) {
|
|
1878
|
+
const prefixedName = `$layout.${action.name}`;
|
|
1879
|
+
mergedActions[prefixedName] = { ...action, name: prefixedName };
|
|
1880
|
+
} else {
|
|
1881
|
+
mergedActions[action.name] = action;
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
const mergedComponents = {
|
|
1885
|
+
...layout.components || {},
|
|
1886
|
+
...page.components || {}
|
|
1887
|
+
};
|
|
1888
|
+
const result = {
|
|
1889
|
+
version: "1.0",
|
|
1890
|
+
state: mergedState,
|
|
1891
|
+
actions: mergedActions,
|
|
1892
|
+
view: composedView
|
|
1893
|
+
};
|
|
1894
|
+
if (page.route) {
|
|
1895
|
+
result.route = page.route;
|
|
1896
|
+
}
|
|
1897
|
+
if (Object.keys(mergedComponents).length > 0) {
|
|
1898
|
+
result.components = mergedComponents;
|
|
1899
|
+
}
|
|
1900
|
+
return result;
|
|
1901
|
+
}
|
|
830
1902
|
export {
|
|
1903
|
+
analyzeLayoutPass,
|
|
831
1904
|
analyzePass,
|
|
832
1905
|
compile,
|
|
1906
|
+
composeLayoutWithPage,
|
|
833
1907
|
createUndefinedVarError2 as createUndefinedVarError,
|
|
1908
|
+
transformLayoutPass,
|
|
834
1909
|
transformPass,
|
|
835
1910
|
validatePass
|
|
836
1911
|
};
|