@constela/compiler 0.1.0 → 0.2.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 +7 -2
- package/dist/index.js +311 -56
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export { createUndefinedVarError } from '@constela/core';
|
|
|
15
15
|
interface AnalysisContext {
|
|
16
16
|
stateNames: Set<string>;
|
|
17
17
|
actionNames: Set<string>;
|
|
18
|
+
componentNames: Set<string>;
|
|
18
19
|
}
|
|
19
20
|
interface AnalyzePassSuccess {
|
|
20
21
|
ok: true;
|
|
@@ -32,14 +33,18 @@ type AnalyzePassResult = AnalyzePassSuccess | AnalyzePassFailure;
|
|
|
32
33
|
*
|
|
33
34
|
* - Collects state names
|
|
34
35
|
* - Collects action names
|
|
36
|
+
* - Collects component names
|
|
35
37
|
* - Validates state references
|
|
36
38
|
* - Validates action references
|
|
37
39
|
* - Validates variable scopes
|
|
40
|
+
* - Validates component references and props
|
|
41
|
+
* - Detects component cycles
|
|
42
|
+
* - Validates param references in component definitions
|
|
38
43
|
*
|
|
39
|
-
* @param
|
|
44
|
+
* @param programAst - Validated AST from validate pass
|
|
40
45
|
* @returns AnalyzePassResult
|
|
41
46
|
*/
|
|
42
|
-
declare function analyzePass(
|
|
47
|
+
declare function analyzePass(programAst: Program): AnalyzePassResult;
|
|
43
48
|
|
|
44
49
|
/**
|
|
45
50
|
* Transform Pass - AST to CompiledProgram transformation
|
package/dist/index.js
CHANGED
|
@@ -20,21 +20,29 @@ import {
|
|
|
20
20
|
createUndefinedActionError,
|
|
21
21
|
createUndefinedVarError,
|
|
22
22
|
createDuplicateActionError,
|
|
23
|
+
createComponentNotFoundError,
|
|
24
|
+
createComponentPropMissingError,
|
|
25
|
+
createComponentCycleError,
|
|
26
|
+
createUndefinedParamError,
|
|
27
|
+
createSchemaError,
|
|
23
28
|
isEventHandler
|
|
24
29
|
} from "@constela/core";
|
|
25
30
|
function buildPath(base, ...segments) {
|
|
26
31
|
return segments.reduce((p, s) => `${p}/${s}`, base);
|
|
27
32
|
}
|
|
28
|
-
function collectContext(
|
|
29
|
-
const stateNames = new Set(Object.keys(
|
|
30
|
-
const actionNames = new Set(
|
|
31
|
-
|
|
33
|
+
function collectContext(ast2) {
|
|
34
|
+
const stateNames = new Set(Object.keys(ast2.state));
|
|
35
|
+
const actionNames = new Set(ast2.actions.map((a) => a.name));
|
|
36
|
+
const componentNames = new Set(
|
|
37
|
+
ast2.components ? Object.keys(ast2.components) : []
|
|
38
|
+
);
|
|
39
|
+
return { stateNames, actionNames, componentNames };
|
|
32
40
|
}
|
|
33
|
-
function checkDuplicateActions(
|
|
41
|
+
function checkDuplicateActions(ast2) {
|
|
34
42
|
const errors = [];
|
|
35
43
|
const seenNames = /* @__PURE__ */ new Set();
|
|
36
|
-
for (let i = 0; i <
|
|
37
|
-
const action =
|
|
44
|
+
for (let i = 0; i < ast2.actions.length; i++) {
|
|
45
|
+
const action = ast2.actions[i];
|
|
38
46
|
if (action === void 0) continue;
|
|
39
47
|
if (seenNames.has(action.name)) {
|
|
40
48
|
errors.push(createDuplicateActionError(action.name, `/actions/${i}`));
|
|
@@ -43,7 +51,7 @@ function checkDuplicateActions(ast) {
|
|
|
43
51
|
}
|
|
44
52
|
return errors;
|
|
45
53
|
}
|
|
46
|
-
function validateExpression(expr, path, context, scope) {
|
|
54
|
+
function validateExpression(expr, path, context, scope, paramScope) {
|
|
47
55
|
const errors = [];
|
|
48
56
|
switch (expr.expr) {
|
|
49
57
|
case "state":
|
|
@@ -56,12 +64,17 @@ function validateExpression(expr, path, context, scope) {
|
|
|
56
64
|
errors.push(createUndefinedVarError(expr.name, path));
|
|
57
65
|
}
|
|
58
66
|
break;
|
|
67
|
+
case "param":
|
|
68
|
+
if (!paramScope || !paramScope.params.has(expr.name)) {
|
|
69
|
+
errors.push(createUndefinedParamError(expr.name, path));
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
59
72
|
case "bin":
|
|
60
|
-
errors.push(...validateExpression(expr.left, buildPath(path, "left"), context, scope));
|
|
61
|
-
errors.push(...validateExpression(expr.right, buildPath(path, "right"), context, scope));
|
|
73
|
+
errors.push(...validateExpression(expr.left, buildPath(path, "left"), context, scope, paramScope));
|
|
74
|
+
errors.push(...validateExpression(expr.right, buildPath(path, "right"), context, scope, paramScope));
|
|
62
75
|
break;
|
|
63
76
|
case "not":
|
|
64
|
-
errors.push(...validateExpression(expr.operand, buildPath(path, "operand"), context, scope));
|
|
77
|
+
errors.push(...validateExpression(expr.operand, buildPath(path, "operand"), context, scope, paramScope));
|
|
65
78
|
break;
|
|
66
79
|
case "lit":
|
|
67
80
|
break;
|
|
@@ -177,8 +190,9 @@ function validateExpressionInEventPayload(expr, path, context, scope) {
|
|
|
177
190
|
}
|
|
178
191
|
return errors;
|
|
179
192
|
}
|
|
180
|
-
function validateViewNode(node, path, context, scope) {
|
|
193
|
+
function validateViewNode(node, path, context, scope, options = { insideComponent: false }) {
|
|
181
194
|
const errors = [];
|
|
195
|
+
const { insideComponent, paramScope } = options;
|
|
182
196
|
switch (node.kind) {
|
|
183
197
|
case "element":
|
|
184
198
|
if (node.props) {
|
|
@@ -199,7 +213,7 @@ function validateViewNode(node, path, context, scope) {
|
|
|
199
213
|
);
|
|
200
214
|
}
|
|
201
215
|
} else {
|
|
202
|
-
errors.push(...validateExpression(propValue, propPath, context, scope));
|
|
216
|
+
errors.push(...validateExpression(propValue, propPath, context, scope, paramScope));
|
|
203
217
|
}
|
|
204
218
|
}
|
|
205
219
|
}
|
|
@@ -208,42 +222,192 @@ function validateViewNode(node, path, context, scope) {
|
|
|
208
222
|
const child = node.children[i];
|
|
209
223
|
if (child === void 0) continue;
|
|
210
224
|
errors.push(
|
|
211
|
-
...validateViewNode(child, buildPath(path, "children", i), context, scope)
|
|
225
|
+
...validateViewNode(child, buildPath(path, "children", i), context, scope, options)
|
|
212
226
|
);
|
|
213
227
|
}
|
|
214
228
|
}
|
|
215
229
|
break;
|
|
216
230
|
case "text":
|
|
217
|
-
errors.push(...validateExpression(node.value, buildPath(path, "value"), context, scope));
|
|
231
|
+
errors.push(...validateExpression(node.value, buildPath(path, "value"), context, scope, paramScope));
|
|
218
232
|
break;
|
|
219
233
|
case "if":
|
|
220
234
|
errors.push(
|
|
221
|
-
...validateExpression(node.condition, buildPath(path, "condition"), context, scope)
|
|
235
|
+
...validateExpression(node.condition, buildPath(path, "condition"), context, scope, paramScope)
|
|
222
236
|
);
|
|
223
|
-
errors.push(...validateViewNode(node.then, buildPath(path, "then"), context, scope));
|
|
237
|
+
errors.push(...validateViewNode(node.then, buildPath(path, "then"), context, scope, options));
|
|
224
238
|
if (node.else) {
|
|
225
|
-
errors.push(...validateViewNode(node.else, buildPath(path, "else"), context, scope));
|
|
239
|
+
errors.push(...validateViewNode(node.else, buildPath(path, "else"), context, scope, options));
|
|
226
240
|
}
|
|
227
241
|
break;
|
|
228
|
-
case "each":
|
|
229
|
-
errors.push(...validateExpression(node.items, buildPath(path, "items"), context, scope));
|
|
242
|
+
case "each": {
|
|
243
|
+
errors.push(...validateExpression(node.items, buildPath(path, "items"), context, scope, paramScope));
|
|
230
244
|
const bodyScope = new Set(scope);
|
|
231
245
|
bodyScope.add(node.as);
|
|
232
246
|
if (node.index) {
|
|
233
247
|
bodyScope.add(node.index);
|
|
234
248
|
}
|
|
235
249
|
if (node.key) {
|
|
236
|
-
errors.push(...validateExpression(node.key, buildPath(path, "key"), context, bodyScope));
|
|
250
|
+
errors.push(...validateExpression(node.key, buildPath(path, "key"), context, bodyScope, paramScope));
|
|
251
|
+
}
|
|
252
|
+
errors.push(...validateViewNode(node.body, buildPath(path, "body"), context, bodyScope, options));
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
case "component": {
|
|
256
|
+
if (!context.componentNames.has(node.name)) {
|
|
257
|
+
errors.push(createComponentNotFoundError(node.name, path));
|
|
258
|
+
} else {
|
|
259
|
+
const componentDef = ast.components?.[node.name];
|
|
260
|
+
if (componentDef) {
|
|
261
|
+
errors.push(
|
|
262
|
+
...validateComponentProps(node, componentDef, path, context, scope, paramScope)
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (node.children) {
|
|
267
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
268
|
+
const child = node.children[i];
|
|
269
|
+
if (child === void 0) continue;
|
|
270
|
+
errors.push(
|
|
271
|
+
...validateViewNode(child, buildPath(path, "children", i), context, scope, options)
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
case "slot":
|
|
278
|
+
if (!insideComponent) {
|
|
279
|
+
errors.push(
|
|
280
|
+
createSchemaError(`Slot can only be used inside component definitions`, path)
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
return errors;
|
|
286
|
+
}
|
|
287
|
+
function validateComponentProps(node, componentDef, path, context, scope, paramScope) {
|
|
288
|
+
const errors = [];
|
|
289
|
+
const params = componentDef.params ?? {};
|
|
290
|
+
const providedProps = node.props ?? {};
|
|
291
|
+
for (const [paramName, paramDef] of Object.entries(params)) {
|
|
292
|
+
const isRequired = paramDef.required !== false;
|
|
293
|
+
if (isRequired && !(paramName in providedProps)) {
|
|
294
|
+
errors.push(
|
|
295
|
+
createComponentPropMissingError(node.name, paramName, buildPath(path, "props"))
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
for (const [propName, propValue] of Object.entries(providedProps)) {
|
|
300
|
+
errors.push(
|
|
301
|
+
...validateExpression(propValue, buildPath(path, "props", propName), context, scope, paramScope)
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
return errors;
|
|
305
|
+
}
|
|
306
|
+
var ast;
|
|
307
|
+
function collectComponentCalls(node) {
|
|
308
|
+
const calls = /* @__PURE__ */ new Set();
|
|
309
|
+
switch (node.kind) {
|
|
310
|
+
case "component":
|
|
311
|
+
calls.add(node.name);
|
|
312
|
+
if (node.children) {
|
|
313
|
+
for (const child of node.children) {
|
|
314
|
+
for (const call of collectComponentCalls(child)) {
|
|
315
|
+
calls.add(call);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
break;
|
|
320
|
+
case "element":
|
|
321
|
+
if (node.children) {
|
|
322
|
+
for (const child of node.children) {
|
|
323
|
+
for (const call of collectComponentCalls(child)) {
|
|
324
|
+
calls.add(call);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
break;
|
|
329
|
+
case "if":
|
|
330
|
+
for (const call of collectComponentCalls(node.then)) {
|
|
331
|
+
calls.add(call);
|
|
332
|
+
}
|
|
333
|
+
if (node.else) {
|
|
334
|
+
for (const call of collectComponentCalls(node.else)) {
|
|
335
|
+
calls.add(call);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
break;
|
|
339
|
+
case "each":
|
|
340
|
+
for (const call of collectComponentCalls(node.body)) {
|
|
341
|
+
calls.add(call);
|
|
237
342
|
}
|
|
238
|
-
errors.push(...validateViewNode(node.body, buildPath(path, "body"), context, bodyScope));
|
|
239
343
|
break;
|
|
344
|
+
case "text":
|
|
345
|
+
case "slot":
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
return calls;
|
|
349
|
+
}
|
|
350
|
+
function detectComponentCycles(programAst, context) {
|
|
351
|
+
if (!programAst.components) return [];
|
|
352
|
+
const errors = [];
|
|
353
|
+
const callGraph = /* @__PURE__ */ new Map();
|
|
354
|
+
for (const [name, def] of Object.entries(programAst.components)) {
|
|
355
|
+
const calls = collectComponentCalls(def.view);
|
|
356
|
+
callGraph.set(name, calls);
|
|
357
|
+
}
|
|
358
|
+
const visited = /* @__PURE__ */ new Set();
|
|
359
|
+
const recStack = /* @__PURE__ */ new Set();
|
|
360
|
+
function dfs(name, path) {
|
|
361
|
+
visited.add(name);
|
|
362
|
+
recStack.add(name);
|
|
363
|
+
const calls = callGraph.get(name) || /* @__PURE__ */ new Set();
|
|
364
|
+
for (const callee of calls) {
|
|
365
|
+
if (!visited.has(callee)) {
|
|
366
|
+
if (dfs(callee, [...path, callee])) return true;
|
|
367
|
+
} else if (recStack.has(callee)) {
|
|
368
|
+
const cycleStart = path.indexOf(callee);
|
|
369
|
+
const cycle = cycleStart >= 0 ? [...path.slice(cycleStart), callee] : [...path, callee];
|
|
370
|
+
errors.push(createComponentCycleError(cycle, `/components/${path[0]}`));
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
recStack.delete(name);
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
for (const name of context.componentNames) {
|
|
378
|
+
if (!visited.has(name)) {
|
|
379
|
+
dfs(name, [name]);
|
|
380
|
+
}
|
|
240
381
|
}
|
|
241
382
|
return errors;
|
|
242
383
|
}
|
|
243
|
-
function
|
|
384
|
+
function validateComponents(programAst, context) {
|
|
244
385
|
const errors = [];
|
|
245
|
-
|
|
246
|
-
|
|
386
|
+
if (!programAst.components) return errors;
|
|
387
|
+
for (const [name, def] of Object.entries(programAst.components)) {
|
|
388
|
+
const paramNames = new Set(
|
|
389
|
+
def.params ? Object.keys(def.params) : []
|
|
390
|
+
);
|
|
391
|
+
const paramScope = {
|
|
392
|
+
params: paramNames,
|
|
393
|
+
componentName: name
|
|
394
|
+
};
|
|
395
|
+
errors.push(
|
|
396
|
+
...validateViewNode(
|
|
397
|
+
def.view,
|
|
398
|
+
buildPath("", "components", name, "view"),
|
|
399
|
+
context,
|
|
400
|
+
/* @__PURE__ */ new Set(),
|
|
401
|
+
{ insideComponent: true, paramScope }
|
|
402
|
+
)
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
return errors;
|
|
406
|
+
}
|
|
407
|
+
function validateActions(programAst, context) {
|
|
408
|
+
const errors = [];
|
|
409
|
+
for (let i = 0; i < programAst.actions.length; i++) {
|
|
410
|
+
const action = programAst.actions[i];
|
|
247
411
|
if (action === void 0) continue;
|
|
248
412
|
for (let j = 0; j < action.steps.length; j++) {
|
|
249
413
|
const step = action.steps[j];
|
|
@@ -255,12 +419,19 @@ function validateActions(ast, context) {
|
|
|
255
419
|
}
|
|
256
420
|
return errors;
|
|
257
421
|
}
|
|
258
|
-
function analyzePass(
|
|
259
|
-
|
|
422
|
+
function analyzePass(programAst) {
|
|
423
|
+
ast = programAst;
|
|
424
|
+
const context = collectContext(programAst);
|
|
260
425
|
const errors = [];
|
|
261
|
-
errors.push(...checkDuplicateActions(
|
|
262
|
-
errors.push(...validateActions(
|
|
263
|
-
errors.push(...
|
|
426
|
+
errors.push(...checkDuplicateActions(programAst));
|
|
427
|
+
errors.push(...validateActions(programAst, context));
|
|
428
|
+
errors.push(...detectComponentCycles(programAst, context));
|
|
429
|
+
errors.push(...validateComponents(programAst, context));
|
|
430
|
+
errors.push(
|
|
431
|
+
...validateViewNode(programAst.view, "/view", context, /* @__PURE__ */ new Set(), {
|
|
432
|
+
insideComponent: false
|
|
433
|
+
})
|
|
434
|
+
);
|
|
264
435
|
if (errors.length > 0) {
|
|
265
436
|
return {
|
|
266
437
|
ok: false,
|
|
@@ -269,14 +440,14 @@ function analyzePass(ast) {
|
|
|
269
440
|
}
|
|
270
441
|
return {
|
|
271
442
|
ok: true,
|
|
272
|
-
ast,
|
|
443
|
+
ast: programAst,
|
|
273
444
|
context
|
|
274
445
|
};
|
|
275
446
|
}
|
|
276
447
|
|
|
277
448
|
// src/passes/transform.ts
|
|
278
449
|
import { isEventHandler as isEventHandler2 } from "@constela/core";
|
|
279
|
-
function transformExpression(expr) {
|
|
450
|
+
function transformExpression(expr, ctx) {
|
|
280
451
|
switch (expr.expr) {
|
|
281
452
|
case "lit":
|
|
282
453
|
return {
|
|
@@ -302,33 +473,60 @@ function transformExpression(expr) {
|
|
|
302
473
|
return {
|
|
303
474
|
expr: "bin",
|
|
304
475
|
op: expr.op,
|
|
305
|
-
left: transformExpression(expr.left),
|
|
306
|
-
right: transformExpression(expr.right)
|
|
476
|
+
left: transformExpression(expr.left, ctx),
|
|
477
|
+
right: transformExpression(expr.right, ctx)
|
|
307
478
|
};
|
|
308
479
|
case "not":
|
|
309
480
|
return {
|
|
310
481
|
expr: "not",
|
|
311
|
-
operand: transformExpression(expr.operand)
|
|
482
|
+
operand: transformExpression(expr.operand, ctx)
|
|
312
483
|
};
|
|
484
|
+
case "param": {
|
|
485
|
+
const paramValue = ctx.currentParams?.[expr.name];
|
|
486
|
+
if (paramValue !== void 0) {
|
|
487
|
+
if (expr.path) {
|
|
488
|
+
if (paramValue.expr === "var") {
|
|
489
|
+
const existingPath = paramValue.path;
|
|
490
|
+
const resultPath = existingPath ? `${existingPath}.${expr.path}` : expr.path;
|
|
491
|
+
return {
|
|
492
|
+
expr: "var",
|
|
493
|
+
name: paramValue.name,
|
|
494
|
+
path: resultPath
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
if (paramValue.expr === "state") {
|
|
498
|
+
return {
|
|
499
|
+
expr: "var",
|
|
500
|
+
name: paramValue.name,
|
|
501
|
+
path: expr.path
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
return paramValue;
|
|
505
|
+
}
|
|
506
|
+
return paramValue;
|
|
507
|
+
}
|
|
508
|
+
return { expr: "lit", value: null };
|
|
509
|
+
}
|
|
313
510
|
}
|
|
314
511
|
}
|
|
315
|
-
function transformEventHandler(handler) {
|
|
512
|
+
function transformEventHandler(handler, ctx) {
|
|
316
513
|
const result = {
|
|
317
514
|
event: handler.event,
|
|
318
515
|
action: handler.action
|
|
319
516
|
};
|
|
320
517
|
if (handler.payload) {
|
|
321
|
-
result.payload = transformExpression(handler.payload);
|
|
518
|
+
result.payload = transformExpression(handler.payload, ctx);
|
|
322
519
|
}
|
|
323
520
|
return result;
|
|
324
521
|
}
|
|
522
|
+
var emptyContext = { components: {} };
|
|
325
523
|
function transformActionStep(step) {
|
|
326
524
|
switch (step.do) {
|
|
327
525
|
case "set":
|
|
328
526
|
return {
|
|
329
527
|
do: "set",
|
|
330
528
|
target: step.target,
|
|
331
|
-
value: transformExpression(step.value)
|
|
529
|
+
value: transformExpression(step.value, emptyContext)
|
|
332
530
|
};
|
|
333
531
|
case "update": {
|
|
334
532
|
const updateStep = {
|
|
@@ -337,20 +535,20 @@ function transformActionStep(step) {
|
|
|
337
535
|
operation: step.operation
|
|
338
536
|
};
|
|
339
537
|
if (step.value) {
|
|
340
|
-
updateStep.value = transformExpression(step.value);
|
|
538
|
+
updateStep.value = transformExpression(step.value, emptyContext);
|
|
341
539
|
}
|
|
342
540
|
return updateStep;
|
|
343
541
|
}
|
|
344
542
|
case "fetch": {
|
|
345
543
|
const fetchStep = {
|
|
346
544
|
do: "fetch",
|
|
347
|
-
url: transformExpression(step.url)
|
|
545
|
+
url: transformExpression(step.url, emptyContext)
|
|
348
546
|
};
|
|
349
547
|
if (step.method) {
|
|
350
548
|
fetchStep.method = step.method;
|
|
351
549
|
}
|
|
352
550
|
if (step.body) {
|
|
353
|
-
fetchStep.body = transformExpression(step.body);
|
|
551
|
+
fetchStep.body = transformExpression(step.body, emptyContext);
|
|
354
552
|
}
|
|
355
553
|
if (step.result) {
|
|
356
554
|
fetchStep.result = step.result;
|
|
@@ -365,7 +563,20 @@ function transformActionStep(step) {
|
|
|
365
563
|
}
|
|
366
564
|
}
|
|
367
565
|
}
|
|
368
|
-
function
|
|
566
|
+
function flattenSlotChildren(children, ctx) {
|
|
567
|
+
const result = [];
|
|
568
|
+
for (const child of children) {
|
|
569
|
+
if (child.kind === "slot") {
|
|
570
|
+
if (ctx.currentChildren && ctx.currentChildren.length > 0) {
|
|
571
|
+
result.push(...ctx.currentChildren);
|
|
572
|
+
}
|
|
573
|
+
} else {
|
|
574
|
+
result.push(transformViewNode(child, ctx));
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return result;
|
|
578
|
+
}
|
|
579
|
+
function transformViewNode(node, ctx) {
|
|
369
580
|
switch (node.kind) {
|
|
370
581
|
case "element": {
|
|
371
582
|
const compiledElement = {
|
|
@@ -376,48 +587,89 @@ function transformViewNode(node) {
|
|
|
376
587
|
compiledElement.props = {};
|
|
377
588
|
for (const [propName, propValue] of Object.entries(node.props)) {
|
|
378
589
|
if (isEventHandler2(propValue)) {
|
|
379
|
-
compiledElement.props[propName] = transformEventHandler(propValue);
|
|
590
|
+
compiledElement.props[propName] = transformEventHandler(propValue, ctx);
|
|
380
591
|
} else {
|
|
381
|
-
compiledElement.props[propName] = transformExpression(propValue);
|
|
592
|
+
compiledElement.props[propName] = transformExpression(propValue, ctx);
|
|
382
593
|
}
|
|
383
594
|
}
|
|
384
595
|
}
|
|
385
596
|
if (node.children && node.children.length > 0) {
|
|
386
|
-
|
|
597
|
+
const flattenedChildren = flattenSlotChildren(node.children, ctx);
|
|
598
|
+
if (flattenedChildren.length > 0) {
|
|
599
|
+
compiledElement.children = flattenedChildren;
|
|
600
|
+
}
|
|
387
601
|
}
|
|
388
602
|
return compiledElement;
|
|
389
603
|
}
|
|
390
604
|
case "text":
|
|
391
605
|
return {
|
|
392
606
|
kind: "text",
|
|
393
|
-
value: transformExpression(node.value)
|
|
607
|
+
value: transformExpression(node.value, ctx)
|
|
394
608
|
};
|
|
395
609
|
case "if": {
|
|
396
610
|
const compiledIf = {
|
|
397
611
|
kind: "if",
|
|
398
|
-
condition: transformExpression(node.condition),
|
|
399
|
-
then: transformViewNode(node.then)
|
|
612
|
+
condition: transformExpression(node.condition, ctx),
|
|
613
|
+
then: transformViewNode(node.then, ctx)
|
|
400
614
|
};
|
|
401
615
|
if (node.else) {
|
|
402
|
-
compiledIf.else = transformViewNode(node.else);
|
|
616
|
+
compiledIf.else = transformViewNode(node.else, ctx);
|
|
403
617
|
}
|
|
404
618
|
return compiledIf;
|
|
405
619
|
}
|
|
406
620
|
case "each": {
|
|
407
621
|
const compiledEach = {
|
|
408
622
|
kind: "each",
|
|
409
|
-
items: transformExpression(node.items),
|
|
623
|
+
items: transformExpression(node.items, ctx),
|
|
410
624
|
as: node.as,
|
|
411
|
-
body: transformViewNode(node.body)
|
|
625
|
+
body: transformViewNode(node.body, ctx)
|
|
412
626
|
};
|
|
413
627
|
if (node.index) {
|
|
414
628
|
compiledEach.index = node.index;
|
|
415
629
|
}
|
|
416
630
|
if (node.key) {
|
|
417
|
-
compiledEach.key = transformExpression(node.key);
|
|
631
|
+
compiledEach.key = transformExpression(node.key, ctx);
|
|
418
632
|
}
|
|
419
633
|
return compiledEach;
|
|
420
634
|
}
|
|
635
|
+
case "component": {
|
|
636
|
+
const def = ctx.components[node.name];
|
|
637
|
+
if (!def) {
|
|
638
|
+
return { kind: "element", tag: "div" };
|
|
639
|
+
}
|
|
640
|
+
const params = {};
|
|
641
|
+
if (node.props) {
|
|
642
|
+
for (const [name, expr] of Object.entries(node.props)) {
|
|
643
|
+
params[name] = transformExpression(expr, ctx);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
const children = [];
|
|
647
|
+
if (node.children && node.children.length > 0) {
|
|
648
|
+
for (const child of node.children) {
|
|
649
|
+
children.push(transformViewNode(child, ctx));
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
const newCtx = {
|
|
653
|
+
...ctx,
|
|
654
|
+
currentParams: params,
|
|
655
|
+
currentChildren: children
|
|
656
|
+
};
|
|
657
|
+
return transformViewNode(def.view, newCtx);
|
|
658
|
+
}
|
|
659
|
+
case "slot": {
|
|
660
|
+
if (ctx.currentChildren && ctx.currentChildren.length > 0) {
|
|
661
|
+
if (ctx.currentChildren.length === 1) {
|
|
662
|
+
const child = ctx.currentChildren[0];
|
|
663
|
+
if (child) return child;
|
|
664
|
+
}
|
|
665
|
+
return {
|
|
666
|
+
kind: "element",
|
|
667
|
+
tag: "span",
|
|
668
|
+
children: ctx.currentChildren
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
return { kind: "text", value: { expr: "lit", value: "" } };
|
|
672
|
+
}
|
|
421
673
|
}
|
|
422
674
|
}
|
|
423
675
|
function transformState(state) {
|
|
@@ -440,12 +692,15 @@ function transformActions(actions) {
|
|
|
440
692
|
}
|
|
441
693
|
return compiledActions;
|
|
442
694
|
}
|
|
443
|
-
function transformPass(
|
|
695
|
+
function transformPass(ast2, _context) {
|
|
696
|
+
const ctx = {
|
|
697
|
+
components: ast2.components || {}
|
|
698
|
+
};
|
|
444
699
|
return {
|
|
445
700
|
version: "1.0",
|
|
446
|
-
state: transformState(
|
|
447
|
-
actions: transformActions(
|
|
448
|
-
view: transformViewNode(
|
|
701
|
+
state: transformState(ast2.state),
|
|
702
|
+
actions: transformActions(ast2.actions),
|
|
703
|
+
view: transformViewNode(ast2.view, ctx)
|
|
449
704
|
};
|
|
450
705
|
}
|
|
451
706
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constela/compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.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.2.0"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@types/node": "^20.10.0",
|