@constela/compiler 0.7.0 → 0.8.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/README.md +172 -0
- package/dist/index.d.ts +50 -6
- package/dist/index.js +575 -39
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# @constela/compiler
|
|
2
|
+
|
|
3
|
+
Transforms Constela JSON programs into optimized runtime code.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @constela/compiler
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
constela compile app.json --out dist/app.compiled.json
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## JSON Input
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"version": "1.0",
|
|
22
|
+
"state": {
|
|
23
|
+
"count": { "type": "number", "initial": 0 }
|
|
24
|
+
},
|
|
25
|
+
"actions": [
|
|
26
|
+
{
|
|
27
|
+
"name": "increment",
|
|
28
|
+
"steps": [{ "do": "update", "target": "count", "operation": "increment" }]
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"view": {
|
|
32
|
+
"kind": "element",
|
|
33
|
+
"tag": "button",
|
|
34
|
+
"props": { "onClick": { "event": "click", "action": "increment" } },
|
|
35
|
+
"children": [{ "kind": "text", "value": { "expr": "state", "name": "count" } }]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Compiler Pipeline
|
|
41
|
+
|
|
42
|
+
The compiler transforms JSON programs through three passes:
|
|
43
|
+
|
|
44
|
+
1. **Validate** - JSON Schema validation
|
|
45
|
+
2. **Analyze** - Semantic analysis (state, actions, components, routes)
|
|
46
|
+
3. **Transform** - AST to optimized runtime program
|
|
47
|
+
|
|
48
|
+
## CompiledProgram Structure
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"version": "1.0",
|
|
53
|
+
"route": {
|
|
54
|
+
"path": "/users/:id",
|
|
55
|
+
"params": ["id"],
|
|
56
|
+
"title": { ... },
|
|
57
|
+
"layout": "MainLayout"
|
|
58
|
+
},
|
|
59
|
+
"lifecycle": {
|
|
60
|
+
"onMount": "loadData",
|
|
61
|
+
"onUnmount": "cleanup"
|
|
62
|
+
},
|
|
63
|
+
"state": {
|
|
64
|
+
"count": { "type": "number", "initial": 0 }
|
|
65
|
+
},
|
|
66
|
+
"actions": {
|
|
67
|
+
"increment": {
|
|
68
|
+
"name": "increment",
|
|
69
|
+
"steps": [{ "do": "update", "target": "count", "operation": "increment" }]
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"view": { ... }
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Layout Compilation
|
|
77
|
+
|
|
78
|
+
Layouts are compiled separately with slot validation:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"version": "1.0",
|
|
83
|
+
"type": "layout",
|
|
84
|
+
"view": {
|
|
85
|
+
"kind": "element",
|
|
86
|
+
"tag": "div",
|
|
87
|
+
"children": [
|
|
88
|
+
{ "kind": "component", "name": "Header" },
|
|
89
|
+
{ "kind": "slot" },
|
|
90
|
+
{ "kind": "component", "name": "Footer" }
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Layout Validations:**
|
|
97
|
+
- At least one slot exists
|
|
98
|
+
- No duplicate named slots
|
|
99
|
+
- No duplicate default slots
|
|
100
|
+
- Slots not inside loops
|
|
101
|
+
|
|
102
|
+
## Error Handling
|
|
103
|
+
|
|
104
|
+
Structured errors with JSON Pointer paths:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"code": "UNDEFINED_STATE",
|
|
109
|
+
"message": "State \"count\" is not defined",
|
|
110
|
+
"path": "/view/children/0/props/onClick"
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Error Codes:**
|
|
115
|
+
- `SCHEMA_INVALID` - JSON Schema validation error
|
|
116
|
+
- `UNDEFINED_STATE` - Reference to undefined state
|
|
117
|
+
- `UNDEFINED_ACTION` - Reference to undefined action
|
|
118
|
+
- `DUPLICATE_ACTION` - Duplicate action name
|
|
119
|
+
- `VAR_UNDEFINED` - Undefined variable reference
|
|
120
|
+
- `COMPONENT_NOT_FOUND` - Undefined component
|
|
121
|
+
- `COMPONENT_PROP_MISSING` - Missing required prop
|
|
122
|
+
- `COMPONENT_CYCLE` - Circular component reference
|
|
123
|
+
- `OPERATION_INVALID_FOR_TYPE` - Invalid operation for state type
|
|
124
|
+
- `LAYOUT_MISSING_SLOT` - Layout missing slot node
|
|
125
|
+
- `LAYOUT_NOT_FOUND` - Referenced layout not found
|
|
126
|
+
|
|
127
|
+
## Internal API
|
|
128
|
+
|
|
129
|
+
> For framework developers only. End users should use the CLI.
|
|
130
|
+
|
|
131
|
+
### compile
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { compile } from '@constela/compiler';
|
|
135
|
+
|
|
136
|
+
const result = compile(jsonInput);
|
|
137
|
+
|
|
138
|
+
if (result.ok) {
|
|
139
|
+
console.log(result.program);
|
|
140
|
+
} else {
|
|
141
|
+
console.error(result.errors);
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Individual Passes
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { validatePass, analyzePass, transformPass } from '@constela/compiler';
|
|
149
|
+
|
|
150
|
+
// Step 1: Validate
|
|
151
|
+
const validated = validatePass(input);
|
|
152
|
+
|
|
153
|
+
// Step 2: Analyze
|
|
154
|
+
const analyzed = analyzePass(validated.program);
|
|
155
|
+
|
|
156
|
+
// Step 3: Transform
|
|
157
|
+
const compiled = transformPass(analyzed.program, analyzed.context);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Layout Compilation
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import { analyzeLayoutPass, transformLayoutPass, composeLayoutWithPage } from '@constela/compiler';
|
|
164
|
+
|
|
165
|
+
const layoutResult = analyzeLayoutPass(layoutProgram);
|
|
166
|
+
const compiledLayout = transformLayoutPass(layoutProgram, layoutResult.context);
|
|
167
|
+
const composedProgram = composeLayoutWithPage(compiledLayout, compiledPage);
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Program, ConstelaError, LayoutProgram, ComponentDef, ViewNode } from '@constela/core';
|
|
1
|
+
import { Program, ConstelaError, Expression, LayoutProgram, ComponentDef, ViewNode } from '@constela/core';
|
|
2
2
|
export { createUndefinedVarError } from '@constela/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -20,6 +20,7 @@ interface AnalysisContext {
|
|
|
20
20
|
importNames: Set<string>;
|
|
21
21
|
dataNames: Set<string>;
|
|
22
22
|
refNames: Set<string>;
|
|
23
|
+
styleNames: Set<string>;
|
|
23
24
|
}
|
|
24
25
|
interface AnalyzePassSuccess {
|
|
25
26
|
ok: true;
|
|
@@ -63,6 +64,7 @@ interface CompiledRouteDefinition {
|
|
|
63
64
|
params: string[];
|
|
64
65
|
title?: CompiledExpression;
|
|
65
66
|
layout?: string;
|
|
67
|
+
layoutParams?: Record<string, Expression>;
|
|
66
68
|
meta?: Record<string, CompiledExpression>;
|
|
67
69
|
}
|
|
68
70
|
interface CompiledLifecycleHooks {
|
|
@@ -87,7 +89,7 @@ interface CompiledAction {
|
|
|
87
89
|
name: string;
|
|
88
90
|
steps: CompiledActionStep[];
|
|
89
91
|
}
|
|
90
|
-
type CompiledActionStep = CompiledSetStep | CompiledUpdateStep | CompiledFetchStep | CompiledStorageStep | CompiledClipboardStep | CompiledNavigateStep | CompiledImportStep | CompiledCallStep | CompiledSubscribeStep | CompiledDisposeStep;
|
|
92
|
+
type CompiledActionStep = CompiledSetStep | CompiledUpdateStep | CompiledFetchStep | CompiledStorageStep | CompiledClipboardStep | CompiledNavigateStep | CompiledImportStep | CompiledCallStep | CompiledSubscribeStep | CompiledDisposeStep | CompiledDomStep | CompiledIfStep;
|
|
91
93
|
interface CompiledSetStep {
|
|
92
94
|
do: 'set';
|
|
93
95
|
target: string;
|
|
@@ -178,7 +180,23 @@ interface CompiledDisposeStep {
|
|
|
178
180
|
do: 'dispose';
|
|
179
181
|
target: CompiledExpression;
|
|
180
182
|
}
|
|
181
|
-
|
|
183
|
+
/**
|
|
184
|
+
* Compiled DOM manipulation step
|
|
185
|
+
*/
|
|
186
|
+
interface CompiledDomStep {
|
|
187
|
+
do: 'dom';
|
|
188
|
+
operation: 'addClass' | 'removeClass' | 'toggleClass' | 'setAttribute' | 'removeAttribute';
|
|
189
|
+
selector: CompiledExpression;
|
|
190
|
+
value?: CompiledExpression;
|
|
191
|
+
attribute?: string;
|
|
192
|
+
}
|
|
193
|
+
interface CompiledIfStep {
|
|
194
|
+
do: 'if';
|
|
195
|
+
condition: CompiledExpression;
|
|
196
|
+
then: CompiledActionStep[];
|
|
197
|
+
else?: CompiledActionStep[];
|
|
198
|
+
}
|
|
199
|
+
type CompiledNode = CompiledElementNode | CompiledTextNode | CompiledIfNode | CompiledEachNode | CompiledMarkdownNode | CompiledCodeNode | CompiledSlotNode;
|
|
182
200
|
interface CompiledElementNode {
|
|
183
201
|
kind: 'element';
|
|
184
202
|
tag: string;
|
|
@@ -213,7 +231,11 @@ interface CompiledCodeNode {
|
|
|
213
231
|
language: CompiledExpression;
|
|
214
232
|
content: CompiledExpression;
|
|
215
233
|
}
|
|
216
|
-
|
|
234
|
+
interface CompiledSlotNode {
|
|
235
|
+
kind: 'slot';
|
|
236
|
+
name?: string;
|
|
237
|
+
}
|
|
238
|
+
type CompiledExpression = CompiledLitExpr | CompiledStateExpr | CompiledVarExpr | CompiledBinExpr | CompiledNotExpr | CompiledCondExpr | CompiledGetExpr | CompiledRouteExpr | CompiledImportExpr | CompiledDataExpr | CompiledRefExpr | CompiledIndexExpr | CompiledParamExpr | CompiledStyleExpr;
|
|
217
239
|
interface CompiledLitExpr {
|
|
218
240
|
expr: 'lit';
|
|
219
241
|
value: string | number | boolean | null | unknown[];
|
|
@@ -221,6 +243,7 @@ interface CompiledLitExpr {
|
|
|
221
243
|
interface CompiledStateExpr {
|
|
222
244
|
expr: 'state';
|
|
223
245
|
name: string;
|
|
246
|
+
path?: string;
|
|
224
247
|
}
|
|
225
248
|
interface CompiledVarExpr {
|
|
226
249
|
expr: 'var';
|
|
@@ -258,6 +281,26 @@ interface CompiledImportExpr {
|
|
|
258
281
|
name: string;
|
|
259
282
|
path?: string;
|
|
260
283
|
}
|
|
284
|
+
interface CompiledDataExpr {
|
|
285
|
+
expr: 'data';
|
|
286
|
+
name: string;
|
|
287
|
+
path?: string;
|
|
288
|
+
}
|
|
289
|
+
interface CompiledIndexExpr {
|
|
290
|
+
expr: 'index';
|
|
291
|
+
base: CompiledExpression;
|
|
292
|
+
key: CompiledExpression;
|
|
293
|
+
}
|
|
294
|
+
interface CompiledParamExpr {
|
|
295
|
+
expr: 'param';
|
|
296
|
+
name: string;
|
|
297
|
+
path?: string;
|
|
298
|
+
}
|
|
299
|
+
interface CompiledStyleExpr {
|
|
300
|
+
expr: 'style';
|
|
301
|
+
name: string;
|
|
302
|
+
variants?: Record<string, CompiledExpression>;
|
|
303
|
+
}
|
|
261
304
|
interface CompiledEventHandler {
|
|
262
305
|
event: string;
|
|
263
306
|
action: string;
|
|
@@ -380,6 +423,7 @@ interface CompiledLayoutProgram {
|
|
|
380
423
|
actions: CompiledAction[];
|
|
381
424
|
view: CompiledNode;
|
|
382
425
|
components?: Record<string, ComponentDef> | undefined;
|
|
426
|
+
importData?: Record<string, unknown>;
|
|
383
427
|
}
|
|
384
428
|
/**
|
|
385
429
|
* Transforms a layout program into a compiled layout
|
|
@@ -388,6 +432,6 @@ declare function transformLayoutPass(layout: LayoutProgram, _context: LayoutAnal
|
|
|
388
432
|
/**
|
|
389
433
|
* Composes a layout with a page, inserting page content into slots
|
|
390
434
|
*/
|
|
391
|
-
declare function composeLayoutWithPage(layout: CompiledProgram, page: CompiledProgram, slots?: Record<string, ViewNode>): CompiledProgram;
|
|
435
|
+
declare function composeLayoutWithPage(layout: CompiledProgram, page: CompiledProgram, layoutParams?: Record<string, Expression>, slots?: Record<string, ViewNode>): CompiledProgram;
|
|
392
436
|
|
|
393
|
-
export { type AnalysisContext, type AnalyzePassFailure, type AnalyzePassResult, type AnalyzePassSuccess, type CompileFailure, type CompileResult, type CompileSuccess, type CompiledAction, type CompiledActionStep, type CompiledBinExpr, type CompiledCallStep, type CompiledClipboardStep, type CompiledCodeNode, type CompiledCondExpr, type CompiledDisposeStep, type CompiledEachNode, type CompiledElementNode, type CompiledEventHandler, type CompiledExpression, type CompiledFetchStep, type CompiledGetExpr, type CompiledIfNode, type CompiledImportExpr, type CompiledImportStep, type CompiledLayoutProgram, type CompiledLifecycleHooks, type CompiledLitExpr, type CompiledMarkdownNode, type CompiledNavigateStep, type CompiledNode, type CompiledNotExpr, type CompiledProgram, type CompiledRefExpr, type CompiledRouteDefinition, type CompiledRouteExpr, type CompiledSetStep, type CompiledStateExpr, type CompiledStorageStep, type CompiledSubscribeStep, type CompiledTextNode, type CompiledUpdateStep, type CompiledVarExpr, type LayoutAnalysisContext, type LayoutAnalysisFailure, type LayoutAnalysisResult, type LayoutAnalysisSuccess, type ValidatePassFailure, type ValidatePassResult, type ValidatePassSuccess, analyzeLayoutPass, analyzePass, compile, composeLayoutWithPage, transformLayoutPass, transformPass, validatePass };
|
|
437
|
+
export { type AnalysisContext, type AnalyzePassFailure, type AnalyzePassResult, type AnalyzePassSuccess, type CompileFailure, type CompileResult, type CompileSuccess, type CompiledAction, type CompiledActionStep, type CompiledBinExpr, type CompiledCallStep, type CompiledClipboardStep, type CompiledCodeNode, type CompiledCondExpr, type CompiledDataExpr, type CompiledDisposeStep, type CompiledDomStep, type CompiledEachNode, type CompiledElementNode, type CompiledEventHandler, type CompiledExpression, type CompiledFetchStep, type CompiledGetExpr, type CompiledIfNode, type CompiledIfStep, type CompiledImportExpr, type CompiledImportStep, type CompiledIndexExpr, type CompiledLayoutProgram, type CompiledLifecycleHooks, type CompiledLitExpr, type CompiledMarkdownNode, type CompiledNavigateStep, type CompiledNode, type CompiledNotExpr, type CompiledProgram, type CompiledRefExpr, type CompiledRouteDefinition, type CompiledRouteExpr, type CompiledSetStep, type CompiledSlotNode, type CompiledStateExpr, type CompiledStorageStep, type CompiledSubscribeStep, type CompiledTextNode, type CompiledUpdateStep, type CompiledVarExpr, type LayoutAnalysisContext, type LayoutAnalysisFailure, type LayoutAnalysisResult, type LayoutAnalysisSuccess, type ValidatePassFailure, type ValidatePassResult, type ValidatePassSuccess, analyzeLayoutPass, analyzePass, compile, composeLayoutWithPage, transformLayoutPass, transformPass, validatePass };
|
package/dist/index.js
CHANGED
|
@@ -42,6 +42,9 @@ import {
|
|
|
42
42
|
createClipboardWriteMissingValueError,
|
|
43
43
|
createInvalidNavigateTargetError,
|
|
44
44
|
createUndefinedRefError,
|
|
45
|
+
createUndefinedStyleError,
|
|
46
|
+
createUndefinedVariantError,
|
|
47
|
+
findSimilarNames,
|
|
45
48
|
isEventHandler,
|
|
46
49
|
DATA_SOURCE_TYPES,
|
|
47
50
|
DATA_TRANSFORMS,
|
|
@@ -53,6 +56,15 @@ import {
|
|
|
53
56
|
function buildPath(base, ...segments) {
|
|
54
57
|
return segments.reduce((p, s) => `${p}/${s}`, base);
|
|
55
58
|
}
|
|
59
|
+
function createErrorOptionsWithSuggestion(name, availableNames) {
|
|
60
|
+
const availableNamesArray = Array.from(availableNames);
|
|
61
|
+
const similarNames = findSimilarNames(name, availableNames);
|
|
62
|
+
const suggestion = similarNames.length > 0 ? `Did you mean '${similarNames[0]}'?` : void 0;
|
|
63
|
+
return {
|
|
64
|
+
suggestion,
|
|
65
|
+
context: { availableNames: availableNamesArray }
|
|
66
|
+
};
|
|
67
|
+
}
|
|
56
68
|
function extractRouteParams(path) {
|
|
57
69
|
const params = [];
|
|
58
70
|
const segments = path.split("/");
|
|
@@ -124,7 +136,10 @@ function collectContext(programAst) {
|
|
|
124
136
|
programAst.data ? Object.keys(programAst.data) : []
|
|
125
137
|
);
|
|
126
138
|
const refNames = collectRefs(programAst.view);
|
|
127
|
-
|
|
139
|
+
const styleNames = new Set(
|
|
140
|
+
programAst.styles ? Object.keys(programAst.styles) : []
|
|
141
|
+
);
|
|
142
|
+
return { stateNames, actionNames, componentNames, routeParams, importNames, dataNames, refNames, styleNames };
|
|
128
143
|
}
|
|
129
144
|
function checkDuplicateActions(ast2) {
|
|
130
145
|
const errors = [];
|
|
@@ -144,7 +159,8 @@ function validateExpression(expr, path, context, scope, paramScope) {
|
|
|
144
159
|
switch (expr.expr) {
|
|
145
160
|
case "state":
|
|
146
161
|
if (!context.stateNames.has(expr.name)) {
|
|
147
|
-
|
|
162
|
+
const errorOptions = createErrorOptionsWithSuggestion(expr.name, context.stateNames);
|
|
163
|
+
errors.push(createUndefinedStateError(expr.name, path, errorOptions));
|
|
148
164
|
}
|
|
149
165
|
break;
|
|
150
166
|
case "var":
|
|
@@ -207,6 +223,32 @@ function validateExpression(expr, path, context, scope, paramScope) {
|
|
|
207
223
|
case "get":
|
|
208
224
|
errors.push(...validateExpression(expr.base, buildPath(path, "base"), context, scope, paramScope));
|
|
209
225
|
break;
|
|
226
|
+
case "style":
|
|
227
|
+
errors.push(...validateStyleExpression(expr, path, context, scope, paramScope));
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
return errors;
|
|
231
|
+
}
|
|
232
|
+
function validateStyleExpression(expr, path, context, scope, paramScope) {
|
|
233
|
+
const errors = [];
|
|
234
|
+
if (!context.styleNames.has(expr.name)) {
|
|
235
|
+
const errorOptions = createErrorOptionsWithSuggestion(expr.name, context.styleNames);
|
|
236
|
+
errors.push(createUndefinedStyleError(expr.name, path, errorOptions));
|
|
237
|
+
return errors;
|
|
238
|
+
}
|
|
239
|
+
if (expr.variants) {
|
|
240
|
+
const stylePreset = ast.styles?.[expr.name];
|
|
241
|
+
const availableVariants = new Set(
|
|
242
|
+
stylePreset?.variants ? Object.keys(stylePreset.variants) : []
|
|
243
|
+
);
|
|
244
|
+
for (const [variantKey, variantValue] of Object.entries(expr.variants)) {
|
|
245
|
+
const variantPath = buildPath(path, "variants", variantKey);
|
|
246
|
+
if (!availableVariants.has(variantKey)) {
|
|
247
|
+
const errorOptions = createErrorOptionsWithSuggestion(variantKey, availableVariants);
|
|
248
|
+
errors.push(createUndefinedVariantError(variantKey, expr.name, variantPath, errorOptions));
|
|
249
|
+
}
|
|
250
|
+
errors.push(...validateExpression(variantValue, variantPath, context, scope, paramScope));
|
|
251
|
+
}
|
|
210
252
|
}
|
|
211
253
|
return errors;
|
|
212
254
|
}
|
|
@@ -215,7 +257,8 @@ function validateActionStep(step, path, context) {
|
|
|
215
257
|
switch (step.do) {
|
|
216
258
|
case "set":
|
|
217
259
|
if (!context.stateNames.has(step.target)) {
|
|
218
|
-
|
|
260
|
+
const errorOptions = createErrorOptionsWithSuggestion(step.target, context.stateNames);
|
|
261
|
+
errors.push(createUndefinedStateError(step.target, buildPath(path, "target"), errorOptions));
|
|
219
262
|
}
|
|
220
263
|
errors.push(
|
|
221
264
|
...validateExpressionStateOnly(step.value, buildPath(path, "value"), context)
|
|
@@ -223,7 +266,8 @@ function validateActionStep(step, path, context) {
|
|
|
223
266
|
break;
|
|
224
267
|
case "update": {
|
|
225
268
|
if (!context.stateNames.has(step.target)) {
|
|
226
|
-
|
|
269
|
+
const errorOptions = createErrorOptionsWithSuggestion(step.target, context.stateNames);
|
|
270
|
+
errors.push(createUndefinedStateError(step.target, buildPath(path, "target"), errorOptions));
|
|
227
271
|
} else {
|
|
228
272
|
const stateField = ast.state[step.target];
|
|
229
273
|
if (stateField) {
|
|
@@ -437,7 +481,8 @@ function validateActionStep(step, path, context) {
|
|
|
437
481
|
...validateExpressionStateOnly(subscribeStep.target, buildPath(path, "target"), context)
|
|
438
482
|
);
|
|
439
483
|
if (!context.actionNames.has(subscribeStep.action)) {
|
|
440
|
-
|
|
484
|
+
const errorOptions = createErrorOptionsWithSuggestion(subscribeStep.action, context.actionNames);
|
|
485
|
+
errors.push(createUndefinedActionError(subscribeStep.action, buildPath(path, "action"), errorOptions));
|
|
441
486
|
}
|
|
442
487
|
break;
|
|
443
488
|
}
|
|
@@ -456,7 +501,8 @@ function validateExpressionStateOnly(expr, path, context) {
|
|
|
456
501
|
switch (expr.expr) {
|
|
457
502
|
case "state":
|
|
458
503
|
if (!context.stateNames.has(expr.name)) {
|
|
459
|
-
|
|
504
|
+
const errorOptions = createErrorOptionsWithSuggestion(expr.name, context.stateNames);
|
|
505
|
+
errors.push(createUndefinedStateError(expr.name, path, errorOptions));
|
|
460
506
|
}
|
|
461
507
|
break;
|
|
462
508
|
case "var":
|
|
@@ -513,6 +559,32 @@ function validateExpressionStateOnly(expr, path, context) {
|
|
|
513
559
|
case "get":
|
|
514
560
|
errors.push(...validateExpressionStateOnly(expr.base, buildPath(path, "base"), context));
|
|
515
561
|
break;
|
|
562
|
+
case "style":
|
|
563
|
+
errors.push(...validateStyleExpressionStateOnly(expr, path, context));
|
|
564
|
+
break;
|
|
565
|
+
}
|
|
566
|
+
return errors;
|
|
567
|
+
}
|
|
568
|
+
function validateStyleExpressionStateOnly(expr, path, context) {
|
|
569
|
+
const errors = [];
|
|
570
|
+
if (!context.styleNames.has(expr.name)) {
|
|
571
|
+
const errorOptions = createErrorOptionsWithSuggestion(expr.name, context.styleNames);
|
|
572
|
+
errors.push(createUndefinedStyleError(expr.name, path, errorOptions));
|
|
573
|
+
return errors;
|
|
574
|
+
}
|
|
575
|
+
if (expr.variants) {
|
|
576
|
+
const stylePreset = ast.styles?.[expr.name];
|
|
577
|
+
const availableVariants = new Set(
|
|
578
|
+
stylePreset?.variants ? Object.keys(stylePreset.variants) : []
|
|
579
|
+
);
|
|
580
|
+
for (const [variantKey, variantValue] of Object.entries(expr.variants)) {
|
|
581
|
+
const variantPath = buildPath(path, "variants", variantKey);
|
|
582
|
+
if (!availableVariants.has(variantKey)) {
|
|
583
|
+
const errorOptions = createErrorOptionsWithSuggestion(variantKey, availableVariants);
|
|
584
|
+
errors.push(createUndefinedVariantError(variantKey, expr.name, variantPath, errorOptions));
|
|
585
|
+
}
|
|
586
|
+
errors.push(...validateExpressionStateOnly(variantValue, variantPath, context));
|
|
587
|
+
}
|
|
516
588
|
}
|
|
517
589
|
return errors;
|
|
518
590
|
}
|
|
@@ -521,7 +593,8 @@ function validateExpressionInEventPayload(expr, path, context, scope) {
|
|
|
521
593
|
switch (expr.expr) {
|
|
522
594
|
case "state":
|
|
523
595
|
if (!context.stateNames.has(expr.name)) {
|
|
524
|
-
|
|
596
|
+
const errorOptions = createErrorOptionsWithSuggestion(expr.name, context.stateNames);
|
|
597
|
+
errors.push(createUndefinedStateError(expr.name, path, errorOptions));
|
|
525
598
|
}
|
|
526
599
|
break;
|
|
527
600
|
case "var":
|
|
@@ -595,6 +668,32 @@ function validateExpressionInEventPayload(expr, path, context, scope) {
|
|
|
595
668
|
...validateExpressionInEventPayload(expr.base, buildPath(path, "base"), context, scope)
|
|
596
669
|
);
|
|
597
670
|
break;
|
|
671
|
+
case "style":
|
|
672
|
+
errors.push(...validateStyleExpressionInEventPayload(expr, path, context, scope));
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
return errors;
|
|
676
|
+
}
|
|
677
|
+
function validateStyleExpressionInEventPayload(expr, path, context, scope) {
|
|
678
|
+
const errors = [];
|
|
679
|
+
if (!context.styleNames.has(expr.name)) {
|
|
680
|
+
const errorOptions = createErrorOptionsWithSuggestion(expr.name, context.styleNames);
|
|
681
|
+
errors.push(createUndefinedStyleError(expr.name, path, errorOptions));
|
|
682
|
+
return errors;
|
|
683
|
+
}
|
|
684
|
+
if (expr.variants) {
|
|
685
|
+
const stylePreset = ast.styles?.[expr.name];
|
|
686
|
+
const availableVariants = new Set(
|
|
687
|
+
stylePreset?.variants ? Object.keys(stylePreset.variants) : []
|
|
688
|
+
);
|
|
689
|
+
for (const [variantKey, variantValue] of Object.entries(expr.variants)) {
|
|
690
|
+
const variantPath = buildPath(path, "variants", variantKey);
|
|
691
|
+
if (!availableVariants.has(variantKey)) {
|
|
692
|
+
const errorOptions = createErrorOptionsWithSuggestion(variantKey, availableVariants);
|
|
693
|
+
errors.push(createUndefinedVariantError(variantKey, expr.name, variantPath, errorOptions));
|
|
694
|
+
}
|
|
695
|
+
errors.push(...validateExpressionInEventPayload(variantValue, variantPath, context, scope));
|
|
696
|
+
}
|
|
598
697
|
}
|
|
599
698
|
return errors;
|
|
600
699
|
}
|
|
@@ -608,7 +707,8 @@ function validateViewNode(node, path, context, scope, options = { insideComponen
|
|
|
608
707
|
const propPath = buildPath(path, "props", propName);
|
|
609
708
|
if (isEventHandler(propValue)) {
|
|
610
709
|
if (!context.actionNames.has(propValue.action)) {
|
|
611
|
-
|
|
710
|
+
const errorOptions = createErrorOptionsWithSuggestion(propValue.action, context.actionNames);
|
|
711
|
+
errors.push(createUndefinedActionError(propValue.action, propPath, errorOptions));
|
|
612
712
|
}
|
|
613
713
|
if (propValue.payload) {
|
|
614
714
|
errors.push(
|
|
@@ -662,7 +762,8 @@ function validateViewNode(node, path, context, scope, options = { insideComponen
|
|
|
662
762
|
}
|
|
663
763
|
case "component": {
|
|
664
764
|
if (!context.componentNames.has(node.name)) {
|
|
665
|
-
|
|
765
|
+
const errorOptions = createErrorOptionsWithSuggestion(node.name, context.componentNames);
|
|
766
|
+
errors.push(createComponentNotFoundError(node.name, path, errorOptions));
|
|
666
767
|
} else {
|
|
667
768
|
const componentDef = ast.components?.[node.name];
|
|
668
769
|
if (componentDef) {
|
|
@@ -839,7 +940,8 @@ function validateLifecycleHooks(lifecycle, context) {
|
|
|
839
940
|
for (const hook of hooks) {
|
|
840
941
|
const actionName = lifecycle[hook];
|
|
841
942
|
if (actionName && !context.actionNames.has(actionName)) {
|
|
842
|
-
|
|
943
|
+
const errorOptions = createErrorOptionsWithSuggestion(actionName, context.actionNames);
|
|
944
|
+
errors.push(createUndefinedActionError(actionName, `/lifecycle/${hook}`, errorOptions));
|
|
843
945
|
}
|
|
844
946
|
}
|
|
845
947
|
return errors;
|
|
@@ -958,11 +1060,16 @@ function transformExpression(expr, ctx) {
|
|
|
958
1060
|
expr: "lit",
|
|
959
1061
|
value: expr.value
|
|
960
1062
|
};
|
|
961
|
-
case "state":
|
|
962
|
-
|
|
1063
|
+
case "state": {
|
|
1064
|
+
const stateExpr = {
|
|
963
1065
|
expr: "state",
|
|
964
1066
|
name: expr.name
|
|
965
1067
|
};
|
|
1068
|
+
if (expr.path) {
|
|
1069
|
+
stateExpr.path = expr.path;
|
|
1070
|
+
}
|
|
1071
|
+
return stateExpr;
|
|
1072
|
+
}
|
|
966
1073
|
case "var": {
|
|
967
1074
|
const varExpr = {
|
|
968
1075
|
expr: "var",
|
|
@@ -1052,6 +1159,25 @@ function transformExpression(expr, ctx) {
|
|
|
1052
1159
|
}
|
|
1053
1160
|
case "ref":
|
|
1054
1161
|
return { expr: "ref", name: expr.name };
|
|
1162
|
+
case "index":
|
|
1163
|
+
return {
|
|
1164
|
+
expr: "index",
|
|
1165
|
+
base: transformExpression(expr.base, ctx),
|
|
1166
|
+
key: transformExpression(expr.key, ctx)
|
|
1167
|
+
};
|
|
1168
|
+
case "style": {
|
|
1169
|
+
const styleExpr = {
|
|
1170
|
+
expr: "style",
|
|
1171
|
+
name: expr.name
|
|
1172
|
+
};
|
|
1173
|
+
if (expr.variants) {
|
|
1174
|
+
styleExpr.variants = {};
|
|
1175
|
+
for (const [key, value] of Object.entries(expr.variants)) {
|
|
1176
|
+
styleExpr.variants[key] = transformExpression(value, ctx);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
return styleExpr;
|
|
1180
|
+
}
|
|
1055
1181
|
}
|
|
1056
1182
|
}
|
|
1057
1183
|
function transformEventHandler(handler, ctx) {
|
|
@@ -1219,6 +1345,16 @@ function transformActionStep(step) {
|
|
|
1219
1345
|
target: transformExpression(disposeStep.target, emptyContext)
|
|
1220
1346
|
};
|
|
1221
1347
|
}
|
|
1348
|
+
case "dom": {
|
|
1349
|
+
const domStep = step;
|
|
1350
|
+
return {
|
|
1351
|
+
do: "dom",
|
|
1352
|
+
operation: domStep.operation,
|
|
1353
|
+
selector: transformExpression(domStep.selector, emptyContext),
|
|
1354
|
+
...domStep.value && { value: transformExpression(domStep.value, emptyContext) },
|
|
1355
|
+
...domStep.attribute && { attribute: domStep.attribute }
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1222
1358
|
}
|
|
1223
1359
|
}
|
|
1224
1360
|
function flattenSlotChildren(children, ctx) {
|
|
@@ -1385,6 +1521,9 @@ function transformRouteDefinition(route, ctx) {
|
|
|
1385
1521
|
if (route.layout) {
|
|
1386
1522
|
compiled.layout = route.layout;
|
|
1387
1523
|
}
|
|
1524
|
+
if (route.layoutParams) {
|
|
1525
|
+
compiled.layoutParams = route.layoutParams;
|
|
1526
|
+
}
|
|
1388
1527
|
if (route.meta) {
|
|
1389
1528
|
compiled.meta = {};
|
|
1390
1529
|
for (const [key, value] of Object.entries(route.meta)) {
|
|
@@ -1685,31 +1824,254 @@ function transformState2(state) {
|
|
|
1685
1824
|
}
|
|
1686
1825
|
return result;
|
|
1687
1826
|
}
|
|
1688
|
-
function
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
// Simplified for now
|
|
1699
|
-
};
|
|
1827
|
+
function transformExpression2(expr) {
|
|
1828
|
+
switch (expr.expr) {
|
|
1829
|
+
case "lit":
|
|
1830
|
+
return { expr: "lit", value: expr.value };
|
|
1831
|
+
case "state":
|
|
1832
|
+
return { expr: "state", name: expr.name };
|
|
1833
|
+
case "var": {
|
|
1834
|
+
const varExpr = { expr: "var", name: expr.name };
|
|
1835
|
+
if (expr.path) {
|
|
1836
|
+
varExpr.path = expr.path;
|
|
1700
1837
|
}
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1838
|
+
return varExpr;
|
|
1839
|
+
}
|
|
1840
|
+
case "bin":
|
|
1841
|
+
return {
|
|
1842
|
+
expr: "bin",
|
|
1843
|
+
op: expr.op,
|
|
1844
|
+
left: transformExpression2(expr.left),
|
|
1845
|
+
right: transformExpression2(expr.right)
|
|
1846
|
+
};
|
|
1847
|
+
case "not":
|
|
1848
|
+
return {
|
|
1849
|
+
expr: "not",
|
|
1850
|
+
operand: transformExpression2(expr.operand)
|
|
1851
|
+
};
|
|
1852
|
+
case "cond":
|
|
1853
|
+
return {
|
|
1854
|
+
expr: "cond",
|
|
1855
|
+
if: transformExpression2(expr.if),
|
|
1856
|
+
then: transformExpression2(expr.then),
|
|
1857
|
+
else: transformExpression2(expr.else)
|
|
1858
|
+
};
|
|
1859
|
+
case "get":
|
|
1860
|
+
return {
|
|
1861
|
+
expr: "get",
|
|
1862
|
+
base: transformExpression2(expr.base),
|
|
1863
|
+
path: expr.path
|
|
1864
|
+
};
|
|
1865
|
+
case "route":
|
|
1866
|
+
return {
|
|
1867
|
+
expr: "route",
|
|
1868
|
+
name: expr.name,
|
|
1869
|
+
source: expr.source ?? "param"
|
|
1870
|
+
};
|
|
1871
|
+
case "import": {
|
|
1872
|
+
const importExpr = { expr: "import", name: expr.name };
|
|
1873
|
+
if (expr.path) {
|
|
1874
|
+
importExpr.path = expr.path;
|
|
1875
|
+
}
|
|
1876
|
+
return importExpr;
|
|
1877
|
+
}
|
|
1878
|
+
case "data": {
|
|
1879
|
+
const dataExpr = { expr: "import", name: expr.name };
|
|
1880
|
+
if (expr.path) {
|
|
1881
|
+
dataExpr.path = expr.path;
|
|
1707
1882
|
}
|
|
1883
|
+
return dataExpr;
|
|
1884
|
+
}
|
|
1885
|
+
case "param": {
|
|
1886
|
+
const paramExpr = { expr: "param", name: expr.name };
|
|
1887
|
+
if (expr.path) {
|
|
1888
|
+
paramExpr.path = expr.path;
|
|
1889
|
+
}
|
|
1890
|
+
return paramExpr;
|
|
1891
|
+
}
|
|
1892
|
+
case "ref":
|
|
1893
|
+
return { expr: "ref", name: expr.name };
|
|
1894
|
+
default:
|
|
1895
|
+
return { expr: "lit", value: null };
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
function transformActionStep2(step) {
|
|
1899
|
+
switch (step.do) {
|
|
1900
|
+
case "set":
|
|
1708
1901
|
return {
|
|
1902
|
+
do: "set",
|
|
1903
|
+
target: step.target,
|
|
1904
|
+
value: transformExpression2(step.value)
|
|
1905
|
+
};
|
|
1906
|
+
case "update": {
|
|
1907
|
+
const updateStep = {
|
|
1908
|
+
do: "update",
|
|
1909
|
+
target: step.target,
|
|
1910
|
+
operation: step.operation
|
|
1911
|
+
};
|
|
1912
|
+
if (step.value) {
|
|
1913
|
+
updateStep.value = transformExpression2(step.value);
|
|
1914
|
+
}
|
|
1915
|
+
if (step.index) {
|
|
1916
|
+
updateStep.index = transformExpression2(step.index);
|
|
1917
|
+
}
|
|
1918
|
+
if (step.deleteCount) {
|
|
1919
|
+
updateStep.deleteCount = transformExpression2(step.deleteCount);
|
|
1920
|
+
}
|
|
1921
|
+
return updateStep;
|
|
1922
|
+
}
|
|
1923
|
+
case "fetch": {
|
|
1924
|
+
const fetchStep = {
|
|
1709
1925
|
do: "fetch",
|
|
1710
|
-
url:
|
|
1926
|
+
url: transformExpression2(step.url)
|
|
1711
1927
|
};
|
|
1712
|
-
|
|
1928
|
+
if (step.method) {
|
|
1929
|
+
fetchStep.method = step.method;
|
|
1930
|
+
}
|
|
1931
|
+
if (step.body) {
|
|
1932
|
+
fetchStep.body = transformExpression2(step.body);
|
|
1933
|
+
}
|
|
1934
|
+
if (step.result) {
|
|
1935
|
+
fetchStep.result = step.result;
|
|
1936
|
+
}
|
|
1937
|
+
if (step.onSuccess) {
|
|
1938
|
+
fetchStep.onSuccess = step.onSuccess.map(transformActionStep2);
|
|
1939
|
+
}
|
|
1940
|
+
if (step.onError) {
|
|
1941
|
+
fetchStep.onError = step.onError.map(transformActionStep2);
|
|
1942
|
+
}
|
|
1943
|
+
return fetchStep;
|
|
1944
|
+
}
|
|
1945
|
+
case "storage": {
|
|
1946
|
+
const storageStep = step;
|
|
1947
|
+
const compiledStorageStep = {
|
|
1948
|
+
do: "storage",
|
|
1949
|
+
operation: storageStep.operation,
|
|
1950
|
+
key: transformExpression2(storageStep.key),
|
|
1951
|
+
storage: storageStep.storage
|
|
1952
|
+
};
|
|
1953
|
+
if (storageStep.value) {
|
|
1954
|
+
compiledStorageStep.value = transformExpression2(storageStep.value);
|
|
1955
|
+
}
|
|
1956
|
+
if (storageStep.result) {
|
|
1957
|
+
compiledStorageStep.result = storageStep.result;
|
|
1958
|
+
}
|
|
1959
|
+
if (storageStep.onSuccess) {
|
|
1960
|
+
compiledStorageStep.onSuccess = storageStep.onSuccess.map(transformActionStep2);
|
|
1961
|
+
}
|
|
1962
|
+
if (storageStep.onError) {
|
|
1963
|
+
compiledStorageStep.onError = storageStep.onError.map(transformActionStep2);
|
|
1964
|
+
}
|
|
1965
|
+
return compiledStorageStep;
|
|
1966
|
+
}
|
|
1967
|
+
case "clipboard": {
|
|
1968
|
+
const clipboardStep = step;
|
|
1969
|
+
const compiledClipboardStep = {
|
|
1970
|
+
do: "clipboard",
|
|
1971
|
+
operation: clipboardStep.operation
|
|
1972
|
+
};
|
|
1973
|
+
if (clipboardStep.value) {
|
|
1974
|
+
compiledClipboardStep.value = transformExpression2(clipboardStep.value);
|
|
1975
|
+
}
|
|
1976
|
+
if (clipboardStep.result) {
|
|
1977
|
+
compiledClipboardStep.result = clipboardStep.result;
|
|
1978
|
+
}
|
|
1979
|
+
if (clipboardStep.onSuccess) {
|
|
1980
|
+
compiledClipboardStep.onSuccess = clipboardStep.onSuccess.map(transformActionStep2);
|
|
1981
|
+
}
|
|
1982
|
+
if (clipboardStep.onError) {
|
|
1983
|
+
compiledClipboardStep.onError = clipboardStep.onError.map(transformActionStep2);
|
|
1984
|
+
}
|
|
1985
|
+
return compiledClipboardStep;
|
|
1986
|
+
}
|
|
1987
|
+
case "navigate": {
|
|
1988
|
+
const navigateStep = step;
|
|
1989
|
+
const compiledNavigateStep = {
|
|
1990
|
+
do: "navigate",
|
|
1991
|
+
url: transformExpression2(navigateStep.url)
|
|
1992
|
+
};
|
|
1993
|
+
if (navigateStep.target) {
|
|
1994
|
+
compiledNavigateStep.target = navigateStep.target;
|
|
1995
|
+
}
|
|
1996
|
+
if (navigateStep.replace !== void 0) {
|
|
1997
|
+
compiledNavigateStep.replace = navigateStep.replace;
|
|
1998
|
+
}
|
|
1999
|
+
return compiledNavigateStep;
|
|
2000
|
+
}
|
|
2001
|
+
case "import": {
|
|
2002
|
+
const importStep = step;
|
|
2003
|
+
const compiledImportStep = {
|
|
2004
|
+
do: "import",
|
|
2005
|
+
module: importStep.module,
|
|
2006
|
+
result: importStep.result
|
|
2007
|
+
};
|
|
2008
|
+
if (importStep.onSuccess) {
|
|
2009
|
+
compiledImportStep.onSuccess = importStep.onSuccess.map(transformActionStep2);
|
|
2010
|
+
}
|
|
2011
|
+
if (importStep.onError) {
|
|
2012
|
+
compiledImportStep.onError = importStep.onError.map(transformActionStep2);
|
|
2013
|
+
}
|
|
2014
|
+
return compiledImportStep;
|
|
2015
|
+
}
|
|
2016
|
+
case "call": {
|
|
2017
|
+
const callStep = step;
|
|
2018
|
+
const compiledCallStep = {
|
|
2019
|
+
do: "call",
|
|
2020
|
+
target: transformExpression2(callStep.target)
|
|
2021
|
+
};
|
|
2022
|
+
if (callStep.args) {
|
|
2023
|
+
compiledCallStep.args = callStep.args.map((arg) => transformExpression2(arg));
|
|
2024
|
+
}
|
|
2025
|
+
if (callStep.result) {
|
|
2026
|
+
compiledCallStep.result = callStep.result;
|
|
2027
|
+
}
|
|
2028
|
+
if (callStep.onSuccess) {
|
|
2029
|
+
compiledCallStep.onSuccess = callStep.onSuccess.map(transformActionStep2);
|
|
2030
|
+
}
|
|
2031
|
+
if (callStep.onError) {
|
|
2032
|
+
compiledCallStep.onError = callStep.onError.map(transformActionStep2);
|
|
2033
|
+
}
|
|
2034
|
+
return compiledCallStep;
|
|
2035
|
+
}
|
|
2036
|
+
case "subscribe": {
|
|
2037
|
+
const subscribeStep = step;
|
|
2038
|
+
return {
|
|
2039
|
+
do: "subscribe",
|
|
2040
|
+
target: transformExpression2(subscribeStep.target),
|
|
2041
|
+
event: subscribeStep.event,
|
|
2042
|
+
action: subscribeStep.action
|
|
2043
|
+
};
|
|
2044
|
+
}
|
|
2045
|
+
case "dispose": {
|
|
2046
|
+
const disposeStep = step;
|
|
2047
|
+
return {
|
|
2048
|
+
do: "dispose",
|
|
2049
|
+
target: transformExpression2(disposeStep.target)
|
|
2050
|
+
};
|
|
2051
|
+
}
|
|
2052
|
+
case "dom": {
|
|
2053
|
+
const domStep = step;
|
|
2054
|
+
return {
|
|
2055
|
+
do: "dom",
|
|
2056
|
+
operation: domStep.operation,
|
|
2057
|
+
selector: transformExpression2(domStep.selector),
|
|
2058
|
+
...domStep.value && { value: transformExpression2(domStep.value) },
|
|
2059
|
+
...domStep.attribute && { attribute: domStep.attribute }
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
default:
|
|
2063
|
+
return {
|
|
2064
|
+
do: "set",
|
|
2065
|
+
target: "_unknown",
|
|
2066
|
+
value: { expr: "lit", value: null }
|
|
2067
|
+
};
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
function transformActions2(actions) {
|
|
2071
|
+
if (!actions) return [];
|
|
2072
|
+
return actions.map((action) => ({
|
|
2073
|
+
name: action.name,
|
|
2074
|
+
steps: action.steps.map(transformActionStep2)
|
|
1713
2075
|
}));
|
|
1714
2076
|
}
|
|
1715
2077
|
function transformViewNode2(node, ctx) {
|
|
@@ -1791,7 +2153,7 @@ function transformLayoutPass(layout, _context) {
|
|
|
1791
2153
|
const ctx = {
|
|
1792
2154
|
components: layout.components || {}
|
|
1793
2155
|
};
|
|
1794
|
-
|
|
2156
|
+
const result = {
|
|
1795
2157
|
version: "1.0",
|
|
1796
2158
|
type: "layout",
|
|
1797
2159
|
state: transformState2(layout.state),
|
|
@@ -1799,17 +2161,161 @@ function transformLayoutPass(layout, _context) {
|
|
|
1799
2161
|
view: transformViewNode2(layout.view, ctx),
|
|
1800
2162
|
components: layout.components
|
|
1801
2163
|
};
|
|
2164
|
+
if (layout.importData && Object.keys(layout.importData).length > 0) {
|
|
2165
|
+
result.importData = layout.importData;
|
|
2166
|
+
}
|
|
2167
|
+
return result;
|
|
1802
2168
|
}
|
|
1803
2169
|
function deepCloneNode(node) {
|
|
1804
2170
|
return JSON.parse(JSON.stringify(node));
|
|
1805
2171
|
}
|
|
2172
|
+
function isParamExpression(value) {
|
|
2173
|
+
return typeof value === "object" && value !== null && value.expr === "param" && typeof value.name === "string";
|
|
2174
|
+
}
|
|
2175
|
+
function resolveParamExpression(paramExpr, layoutParams) {
|
|
2176
|
+
const resolvedValue = layoutParams[paramExpr.name];
|
|
2177
|
+
if (!resolvedValue) {
|
|
2178
|
+
return { expr: "lit", value: null };
|
|
2179
|
+
}
|
|
2180
|
+
if (paramExpr.path) {
|
|
2181
|
+
return {
|
|
2182
|
+
expr: "get",
|
|
2183
|
+
base: resolvedValue,
|
|
2184
|
+
path: paramExpr.path
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2187
|
+
return resolvedValue;
|
|
2188
|
+
}
|
|
2189
|
+
function resolveExpressionValue(value, layoutParams) {
|
|
2190
|
+
if (!value || typeof value !== "object") {
|
|
2191
|
+
return value;
|
|
2192
|
+
}
|
|
2193
|
+
if (isParamExpression(value)) {
|
|
2194
|
+
return resolveParamExpression(value, layoutParams);
|
|
2195
|
+
}
|
|
2196
|
+
if (Array.isArray(value)) {
|
|
2197
|
+
return value.map((item) => resolveExpressionValue(item, layoutParams));
|
|
2198
|
+
}
|
|
2199
|
+
const obj = value;
|
|
2200
|
+
const result = {};
|
|
2201
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
2202
|
+
result[key] = resolveExpressionValue(val, layoutParams);
|
|
2203
|
+
}
|
|
2204
|
+
return result;
|
|
2205
|
+
}
|
|
2206
|
+
function resolvePropsParams(props, layoutParams) {
|
|
2207
|
+
const result = {};
|
|
2208
|
+
for (const [key, value] of Object.entries(props)) {
|
|
2209
|
+
result[key] = resolveExpressionValue(value, layoutParams);
|
|
2210
|
+
}
|
|
2211
|
+
return result;
|
|
2212
|
+
}
|
|
2213
|
+
function resolveParamExpressions(node, layoutParams) {
|
|
2214
|
+
switch (node.kind) {
|
|
2215
|
+
case "element": {
|
|
2216
|
+
const elementNode = node;
|
|
2217
|
+
const result = {
|
|
2218
|
+
kind: "element",
|
|
2219
|
+
tag: elementNode.tag
|
|
2220
|
+
};
|
|
2221
|
+
if (elementNode.props) {
|
|
2222
|
+
result.props = resolvePropsParams(
|
|
2223
|
+
elementNode.props,
|
|
2224
|
+
layoutParams
|
|
2225
|
+
);
|
|
2226
|
+
}
|
|
2227
|
+
if (elementNode.children && elementNode.children.length > 0) {
|
|
2228
|
+
result.children = elementNode.children.map(
|
|
2229
|
+
(child) => resolveParamExpressions(child, layoutParams)
|
|
2230
|
+
);
|
|
2231
|
+
}
|
|
2232
|
+
return result;
|
|
2233
|
+
}
|
|
2234
|
+
case "text": {
|
|
2235
|
+
const textNode = node;
|
|
2236
|
+
return {
|
|
2237
|
+
kind: "text",
|
|
2238
|
+
value: resolveExpressionValue(textNode.value, layoutParams)
|
|
2239
|
+
};
|
|
2240
|
+
}
|
|
2241
|
+
case "if": {
|
|
2242
|
+
const ifNode = node;
|
|
2243
|
+
const result = {
|
|
2244
|
+
kind: "if",
|
|
2245
|
+
condition: resolveExpressionValue(ifNode.condition, layoutParams),
|
|
2246
|
+
then: resolveParamExpressions(ifNode.then, layoutParams)
|
|
2247
|
+
};
|
|
2248
|
+
if (ifNode.else) {
|
|
2249
|
+
result.else = resolveParamExpressions(
|
|
2250
|
+
ifNode.else,
|
|
2251
|
+
layoutParams
|
|
2252
|
+
);
|
|
2253
|
+
}
|
|
2254
|
+
return result;
|
|
2255
|
+
}
|
|
2256
|
+
case "each": {
|
|
2257
|
+
const eachNode = node;
|
|
2258
|
+
return {
|
|
2259
|
+
kind: "each",
|
|
2260
|
+
items: resolveExpressionValue(eachNode.items, layoutParams),
|
|
2261
|
+
as: eachNode.as,
|
|
2262
|
+
body: resolveParamExpressions(eachNode.body, layoutParams)
|
|
2263
|
+
};
|
|
2264
|
+
}
|
|
2265
|
+
default:
|
|
2266
|
+
return node;
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
function processNamedSlotsOnly(node, namedContent) {
|
|
2270
|
+
if (node.kind === "slot") {
|
|
2271
|
+
const slotName = node.name;
|
|
2272
|
+
if (slotName && namedContent[slotName]) {
|
|
2273
|
+
return deepCloneNode(namedContent[slotName]);
|
|
2274
|
+
}
|
|
2275
|
+
return node;
|
|
2276
|
+
}
|
|
2277
|
+
if (node.kind === "element") {
|
|
2278
|
+
const children = node.children;
|
|
2279
|
+
if (children && children.length > 0) {
|
|
2280
|
+
const newChildren = children.map((child) => processNamedSlotsOnly(child, namedContent));
|
|
2281
|
+
return {
|
|
2282
|
+
...node,
|
|
2283
|
+
children: newChildren
|
|
2284
|
+
};
|
|
2285
|
+
}
|
|
2286
|
+
return node;
|
|
2287
|
+
}
|
|
2288
|
+
if (node.kind === "if") {
|
|
2289
|
+
const ifNode = node;
|
|
2290
|
+
const result = {
|
|
2291
|
+
...node,
|
|
2292
|
+
then: processNamedSlotsOnly(ifNode.then, namedContent)
|
|
2293
|
+
};
|
|
2294
|
+
if (ifNode.else) {
|
|
2295
|
+
result.else = processNamedSlotsOnly(ifNode.else, namedContent);
|
|
2296
|
+
}
|
|
2297
|
+
return result;
|
|
2298
|
+
}
|
|
2299
|
+
if (node.kind === "each") {
|
|
2300
|
+
const eachNode = node;
|
|
2301
|
+
return {
|
|
2302
|
+
...node,
|
|
2303
|
+
body: processNamedSlotsOnly(eachNode.body, namedContent)
|
|
2304
|
+
};
|
|
2305
|
+
}
|
|
2306
|
+
return node;
|
|
2307
|
+
}
|
|
1806
2308
|
function replaceSlots(node, defaultContent, namedContent) {
|
|
1807
2309
|
if (node.kind === "slot") {
|
|
1808
2310
|
const slotName = node.name;
|
|
1809
2311
|
if (slotName && namedContent?.[slotName]) {
|
|
1810
2312
|
return deepCloneNode(namedContent[slotName]);
|
|
1811
2313
|
}
|
|
1812
|
-
|
|
2314
|
+
const clonedDefault = deepCloneNode(defaultContent);
|
|
2315
|
+
if (namedContent && Object.keys(namedContent).length > 0) {
|
|
2316
|
+
return processNamedSlotsOnly(clonedDefault, namedContent);
|
|
2317
|
+
}
|
|
2318
|
+
return clonedDefault;
|
|
1813
2319
|
}
|
|
1814
2320
|
if (node.kind === "element") {
|
|
1815
2321
|
const children = node.children;
|
|
@@ -1842,11 +2348,31 @@ function replaceSlots(node, defaultContent, namedContent) {
|
|
|
1842
2348
|
}
|
|
1843
2349
|
return node;
|
|
1844
2350
|
}
|
|
1845
|
-
function
|
|
1846
|
-
|
|
1847
|
-
const
|
|
1848
|
-
|
|
1849
|
-
|
|
2351
|
+
function extractMdxSlotsFromImportData(importData) {
|
|
2352
|
+
if (!importData) return void 0;
|
|
2353
|
+
for (const [, dataSource] of Object.entries(importData)) {
|
|
2354
|
+
if (!Array.isArray(dataSource)) continue;
|
|
2355
|
+
for (const item of dataSource) {
|
|
2356
|
+
if (typeof item === "object" && item !== null && "content" in item && typeof item.content === "object") {
|
|
2357
|
+
const content = item.content;
|
|
2358
|
+
return { "mdx-content": content };
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
return void 0;
|
|
2363
|
+
}
|
|
2364
|
+
function composeLayoutWithPage(layout, page, layoutParams, slots) {
|
|
2365
|
+
let layoutView = deepCloneNode(layout.view);
|
|
2366
|
+
const resolvedParams = layoutParams ?? {};
|
|
2367
|
+
layoutView = resolveParamExpressions(layoutView, resolvedParams);
|
|
2368
|
+
let namedContent;
|
|
2369
|
+
if (slots) {
|
|
2370
|
+
namedContent = Object.fromEntries(
|
|
2371
|
+
Object.entries(slots).map(([name, node]) => [name, node])
|
|
2372
|
+
);
|
|
2373
|
+
} else {
|
|
2374
|
+
namedContent = extractMdxSlotsFromImportData(page.importData);
|
|
2375
|
+
}
|
|
1850
2376
|
const composedView = replaceSlots(layoutView, page.view, namedContent);
|
|
1851
2377
|
const mergedState = {};
|
|
1852
2378
|
for (const [name, field] of Object.entries(page.state)) {
|
|
@@ -1897,6 +2423,16 @@ function composeLayoutWithPage(layout, page, slots) {
|
|
|
1897
2423
|
if (Object.keys(mergedComponents).length > 0) {
|
|
1898
2424
|
result.components = mergedComponents;
|
|
1899
2425
|
}
|
|
2426
|
+
const mergedImportData = {
|
|
2427
|
+
...layout.importData || {},
|
|
2428
|
+
...page.importData || {}
|
|
2429
|
+
};
|
|
2430
|
+
if (Object.keys(mergedImportData).length > 0) {
|
|
2431
|
+
result.importData = mergedImportData;
|
|
2432
|
+
}
|
|
2433
|
+
if (page.lifecycle) {
|
|
2434
|
+
result.lifecycle = page.lifecycle;
|
|
2435
|
+
}
|
|
1900
2436
|
return result;
|
|
1901
2437
|
}
|
|
1902
2438
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constela/compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Compiler for Constela UI framework - AST to Program transformation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"dist"
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@constela/core": "0.
|
|
18
|
+
"@constela/core": "0.8.0"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@types/node": "^20.10.0",
|