@zenithbuild/compiler 1.0.2

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.
Files changed (145) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/dist/build-analyzer.d.ts +44 -0
  4. package/dist/build-analyzer.js +87 -0
  5. package/dist/bundler.d.ts +31 -0
  6. package/dist/bundler.js +86 -0
  7. package/dist/core/components/index.d.ts +9 -0
  8. package/dist/core/components/index.js +13 -0
  9. package/dist/core/config/index.d.ts +11 -0
  10. package/dist/core/config/index.js +10 -0
  11. package/dist/core/config/loader.d.ts +17 -0
  12. package/dist/core/config/loader.js +60 -0
  13. package/dist/core/config/types.d.ts +98 -0
  14. package/dist/core/config/types.js +32 -0
  15. package/dist/core/index.d.ts +7 -0
  16. package/dist/core/index.js +6 -0
  17. package/dist/core/lifecycle/index.d.ts +16 -0
  18. package/dist/core/lifecycle/index.js +19 -0
  19. package/dist/core/lifecycle/zen-mount.d.ts +66 -0
  20. package/dist/core/lifecycle/zen-mount.js +151 -0
  21. package/dist/core/lifecycle/zen-unmount.d.ts +54 -0
  22. package/dist/core/lifecycle/zen-unmount.js +76 -0
  23. package/dist/core/plugins/bridge.d.ts +116 -0
  24. package/dist/core/plugins/bridge.js +121 -0
  25. package/dist/core/plugins/index.d.ts +6 -0
  26. package/dist/core/plugins/index.js +6 -0
  27. package/dist/core/plugins/registry.d.ts +67 -0
  28. package/dist/core/plugins/registry.js +113 -0
  29. package/dist/core/reactivity/index.d.ts +30 -0
  30. package/dist/core/reactivity/index.js +33 -0
  31. package/dist/core/reactivity/tracking.d.ts +74 -0
  32. package/dist/core/reactivity/tracking.js +136 -0
  33. package/dist/core/reactivity/zen-batch.d.ts +45 -0
  34. package/dist/core/reactivity/zen-batch.js +54 -0
  35. package/dist/core/reactivity/zen-effect.d.ts +48 -0
  36. package/dist/core/reactivity/zen-effect.js +98 -0
  37. package/dist/core/reactivity/zen-memo.d.ts +43 -0
  38. package/dist/core/reactivity/zen-memo.js +100 -0
  39. package/dist/core/reactivity/zen-ref.d.ts +44 -0
  40. package/dist/core/reactivity/zen-ref.js +34 -0
  41. package/dist/core/reactivity/zen-signal.d.ts +48 -0
  42. package/dist/core/reactivity/zen-signal.js +84 -0
  43. package/dist/core/reactivity/zen-state.d.ts +35 -0
  44. package/dist/core/reactivity/zen-state.js +147 -0
  45. package/dist/core/reactivity/zen-untrack.d.ts +38 -0
  46. package/dist/core/reactivity/zen-untrack.js +41 -0
  47. package/dist/css/index.d.ts +73 -0
  48. package/dist/css/index.js +246 -0
  49. package/dist/discovery/componentDiscovery.d.ts +42 -0
  50. package/dist/discovery/componentDiscovery.js +56 -0
  51. package/dist/discovery/layouts.d.ts +13 -0
  52. package/dist/discovery/layouts.js +41 -0
  53. package/dist/errors/compilerError.d.ts +31 -0
  54. package/dist/errors/compilerError.js +51 -0
  55. package/dist/finalize/finalizeOutput.d.ts +32 -0
  56. package/dist/finalize/finalizeOutput.js +62 -0
  57. package/dist/finalize/generateFinalBundle.d.ts +24 -0
  58. package/dist/finalize/generateFinalBundle.js +68 -0
  59. package/dist/index.d.ts +36 -0
  60. package/dist/index.js +51 -0
  61. package/dist/ir/types.d.ts +181 -0
  62. package/dist/ir/types.js +8 -0
  63. package/dist/output/types.d.ts +30 -0
  64. package/dist/output/types.js +6 -0
  65. package/dist/parse/detectMapExpressions.d.ts +45 -0
  66. package/dist/parse/detectMapExpressions.js +77 -0
  67. package/dist/parse/parseScript.d.ts +8 -0
  68. package/dist/parse/parseScript.js +36 -0
  69. package/dist/parse/parseTemplate.d.ts +11 -0
  70. package/dist/parse/parseTemplate.js +487 -0
  71. package/dist/parse/parseZenFile.d.ts +11 -0
  72. package/dist/parse/parseZenFile.js +50 -0
  73. package/dist/parse/scriptAnalysis.d.ts +25 -0
  74. package/dist/parse/scriptAnalysis.js +60 -0
  75. package/dist/parse/trackLoopContext.d.ts +20 -0
  76. package/dist/parse/trackLoopContext.js +62 -0
  77. package/dist/parseZenFile.d.ts +10 -0
  78. package/dist/parseZenFile.js +55 -0
  79. package/dist/runtime/analyzeAndEmit.d.ts +20 -0
  80. package/dist/runtime/analyzeAndEmit.js +70 -0
  81. package/dist/runtime/build.d.ts +6 -0
  82. package/dist/runtime/build.js +13 -0
  83. package/dist/runtime/bundle-generator.d.ts +27 -0
  84. package/dist/runtime/bundle-generator.js +1263 -0
  85. package/dist/runtime/client-runtime.d.ts +41 -0
  86. package/dist/runtime/client-runtime.js +397 -0
  87. package/dist/runtime/dataExposure.d.ts +52 -0
  88. package/dist/runtime/dataExposure.js +227 -0
  89. package/dist/runtime/generateDOM.d.ts +21 -0
  90. package/dist/runtime/generateDOM.js +194 -0
  91. package/dist/runtime/generateHydrationBundle.d.ts +15 -0
  92. package/dist/runtime/generateHydrationBundle.js +399 -0
  93. package/dist/runtime/hydration.d.ts +53 -0
  94. package/dist/runtime/hydration.js +271 -0
  95. package/dist/runtime/navigation.d.ts +58 -0
  96. package/dist/runtime/navigation.js +372 -0
  97. package/dist/runtime/serve.d.ts +13 -0
  98. package/dist/runtime/serve.js +76 -0
  99. package/dist/runtime/thinRuntime.d.ts +23 -0
  100. package/dist/runtime/thinRuntime.js +158 -0
  101. package/dist/runtime/transformIR.d.ts +19 -0
  102. package/dist/runtime/transformIR.js +285 -0
  103. package/dist/runtime/wrapExpression.d.ts +24 -0
  104. package/dist/runtime/wrapExpression.js +76 -0
  105. package/dist/runtime/wrapExpressionWithLoop.d.ts +17 -0
  106. package/dist/runtime/wrapExpressionWithLoop.js +75 -0
  107. package/dist/spa-build.d.ts +26 -0
  108. package/dist/spa-build.js +866 -0
  109. package/dist/ssg-build.d.ts +32 -0
  110. package/dist/ssg-build.js +408 -0
  111. package/dist/test/analyze-emit.test.d.ts +1 -0
  112. package/dist/test/analyze-emit.test.js +88 -0
  113. package/dist/test/bundler-contract.test.d.ts +1 -0
  114. package/dist/test/bundler-contract.test.js +137 -0
  115. package/dist/test/compiler-authority.test.d.ts +1 -0
  116. package/dist/test/compiler-authority.test.js +90 -0
  117. package/dist/test/component-instance-test.d.ts +1 -0
  118. package/dist/test/component-instance-test.js +115 -0
  119. package/dist/test/error-native-bridge.test.d.ts +1 -0
  120. package/dist/test/error-native-bridge.test.js +51 -0
  121. package/dist/test/error-serialization.test.d.ts +1 -0
  122. package/dist/test/error-serialization.test.js +38 -0
  123. package/dist/test/macro-inlining.test.d.ts +1 -0
  124. package/dist/test/macro-inlining.test.js +178 -0
  125. package/dist/test/validate-test.d.ts +6 -0
  126. package/dist/test/validate-test.js +95 -0
  127. package/dist/transform/classifyExpression.d.ts +46 -0
  128. package/dist/transform/classifyExpression.js +354 -0
  129. package/dist/transform/componentResolver.d.ts +15 -0
  130. package/dist/transform/componentResolver.js +30 -0
  131. package/dist/transform/expressionTransformer.d.ts +19 -0
  132. package/dist/transform/expressionTransformer.js +333 -0
  133. package/dist/transform/fragmentLowering.d.ts +25 -0
  134. package/dist/transform/fragmentLowering.js +468 -0
  135. package/dist/transform/layoutProcessor.d.ts +5 -0
  136. package/dist/transform/layoutProcessor.js +34 -0
  137. package/dist/transform/transformTemplate.d.ts +11 -0
  138. package/dist/transform/transformTemplate.js +33 -0
  139. package/dist/validate/invariants.d.ts +23 -0
  140. package/dist/validate/invariants.js +55 -0
  141. package/native/compiler-native/compiler-native.node +0 -0
  142. package/native/compiler-native/index.d.ts +113 -0
  143. package/native/compiler-native/index.js +19 -0
  144. package/native/compiler-native/package.json +19 -0
  145. package/package.json +49 -0
@@ -0,0 +1,90 @@
1
+ import { expect, test, describe } from "bun:test";
2
+ import { analyzeAndEmit } from "../runtime/analyzeAndEmit";
3
+ function createIR(templateNodes = [], expressions = [], script = "") {
4
+ return {
5
+ filePath: "test.zen",
6
+ template: {
7
+ raw: "",
8
+ nodes: templateNodes,
9
+ expressions: expressions
10
+ },
11
+ script: {
12
+ raw: script,
13
+ attributes: {}
14
+ },
15
+ styles: [],
16
+ componentScripts: []
17
+ };
18
+ }
19
+ describe("Rust Compiler Authority", () => {
20
+ test("JSX transformation into __zenith.h", async () => {
21
+ const ir = createIR([], [{ id: "expr_jsx", code: "<div><span>Hello</span></div>" }], "state count = 0;");
22
+ const result = await analyzeAndEmit(ir);
23
+ const bundle = result.bundle;
24
+ // Should contain native indicators (like state. prefix or h() calls)
25
+ expect(bundle).toContain("state");
26
+ // Should contain __zenith.h calls in expressions
27
+ expect(result.expressions).toContain('__zenith.h("div"');
28
+ expect(result.expressions).toContain('__zenith.h("span"');
29
+ expect(result.expressions).toContain('"Hello"');
30
+ // Should NOT contain raw HTML tags
31
+ expect(result.expressions).not.toContain("<div>");
32
+ });
33
+ test("State identifier renaming - state. prefix", async () => {
34
+ const ir = createIR([], [{ id: "expr_state", code: "count + 1" }], "state count = 0;");
35
+ const result = await analyzeAndEmit(ir);
36
+ // Should be renamed to state.count
37
+ expect(result.expressions).toContain("state.count");
38
+ // It shouldn't be plain count + 1 (except maybe in some comment if we had one)
39
+ expect(result.expressions).not.toContain("return (count + 1)");
40
+ });
41
+ test("Local variables (loop vars) are NOT prefixed with state.", async () => {
42
+ const ir = createIR([
43
+ {
44
+ type: "loop-fragment",
45
+ itemVar: "item",
46
+ indexVar: "i",
47
+ source: "items",
48
+ body: [],
49
+ location: { line: 1, column: 1 }
50
+ }
51
+ ], [{ id: "expr_loop", code: "item.name + i" }], "state items = [];");
52
+ const result = await analyzeAndEmit(ir);
53
+ // item and i should STAY AS IS
54
+ expect(result.expressions).toContain("item.name + i");
55
+ expect(result.expressions).not.toContain("state.item");
56
+ expect(result.expressions).not.toContain("state.i");
57
+ });
58
+ test("Nested JSX with state and local variables", async () => {
59
+ const ir = createIR([
60
+ {
61
+ type: "loop-fragment",
62
+ itemVar: "item",
63
+ indexVar: "i",
64
+ source: "items",
65
+ body: [],
66
+ location: { line: 1, column: 1 }
67
+ }
68
+ ], [{ id: "expr_complex", code: "items.map(it => <div class={active ? 'active' : ''}>{it.name}</div>)" }], "state items = []; state active = true;");
69
+ const result = await analyzeAndEmit(ir);
70
+ // 1. JSX lowered
71
+ expect(result.expressions).toContain('__zenith.h("div"');
72
+ // 2. state.active
73
+ expect(result.expressions).toContain('state.active');
74
+ // 2. state.active
75
+ expect(result.expressions).toContain('class: state.active');
76
+ // 3. state.items
77
+ expect(result.expressions).toContain('state.items.map');
78
+ // 4. it.name stays as is (it's local to the map)
79
+ expect(result.expressions).toContain('[it.name]');
80
+ expect(result.expressions).not.toContain(': state.it');
81
+ });
82
+ test("JSX Attributes transformation", async () => {
83
+ const ir = createIR([], [{ id: "expr_attr", code: "<button disabled={loading} onclick={() => count++}>Click</button>" }], "state loading = false; state count = 0;");
84
+ const result = await analyzeAndEmit(ir);
85
+ expect(result.expressions).toContain('__zenith.h("button"');
86
+ // Attributes should be in an object
87
+ expect(result.expressions).toContain('disabled: state.loading');
88
+ expect(result.expressions).toContain('onclick: () => state.count++');
89
+ });
90
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,115 @@
1
+ import { test, expect } from "bun:test";
2
+ import { resolveComponentsInIR } from "../transform/componentResolver";
3
+ // Helper to create a basic IR
4
+ function createIR(nodes, expressions = []) {
5
+ return {
6
+ filePath: "test.zen",
7
+ template: {
8
+ raw: "",
9
+ nodes,
10
+ expressions
11
+ },
12
+ script: { raw: "", attributes: {} },
13
+ styles: [],
14
+ componentScripts: []
15
+ };
16
+ }
17
+ // Helper to create component metadata
18
+ function createComponent(name, nodes, expressions) {
19
+ return {
20
+ name,
21
+ path: `${name}.zen`,
22
+ template: "",
23
+ nodes,
24
+ expressions,
25
+ slots: [],
26
+ props: [],
27
+ styles: [],
28
+ script: null,
29
+ scriptAttributes: null,
30
+ hasScript: false,
31
+ hasStyles: false
32
+ };
33
+ }
34
+ test("Component instantiation generates unique instance expression IDs", () => {
35
+ // Define a component "Card" with one expression {title}
36
+ // usage: <div>{title}</div>
37
+ const cardExpr = { id: "expr_card_1", code: "title", location: { line: 1, column: 1 } };
38
+ const cardNodes = [
39
+ {
40
+ type: "element",
41
+ tag: "div",
42
+ attributes: [],
43
+ children: [
44
+ {
45
+ type: "expression",
46
+ expression: "expr_card_1",
47
+ location: { line: 1, column: 1 }
48
+ }
49
+ ],
50
+ location: { line: 1, column: 1 }
51
+ }
52
+ ];
53
+ const components = new Map();
54
+ components.set("Card", createComponent("Card", cardNodes, [cardExpr]));
55
+ // Create a page using <Card /> twice
56
+ // <root>
57
+ // <Card />
58
+ // <Card />
59
+ // </root>
60
+ const pageNodes = [
61
+ {
62
+ type: "element",
63
+ tag: "root",
64
+ attributes: [],
65
+ children: [
66
+ {
67
+ type: "component",
68
+ name: "Card",
69
+ attributes: [],
70
+ children: [],
71
+ location: { line: 1, column: 1 }
72
+ },
73
+ {
74
+ type: "component",
75
+ name: "Card",
76
+ attributes: [],
77
+ children: [],
78
+ location: { line: 2, column: 1 }
79
+ }
80
+ ],
81
+ location: { line: 0, column: 0 }
82
+ }
83
+ ];
84
+ const ir = createIR(pageNodes);
85
+ // Resolve components
86
+ const resolvedIR = resolveComponentsInIR(ir, components);
87
+ // Assertions
88
+ const root = resolvedIR.template.nodes[0];
89
+ expect(root.type).toBe("element");
90
+ expect(root.children.length).toBe(2);
91
+ const card1 = root.children[0];
92
+ const card2 = root.children[1];
93
+ // Check structure
94
+ expect(card1.type).toBe("element"); // Resolved to div
95
+ expect(card2.type).toBe("element");
96
+ // Check expression nodes inside
97
+ const exprNode1 = card1.children[0];
98
+ const exprNode2 = card2.children[0];
99
+ expect(exprNode1.type).toBe("expression");
100
+ expect(exprNode2.type).toBe("expression");
101
+ const id1 = exprNode1.expression;
102
+ const id2 = exprNode2.expression;
103
+ console.log(`Card 1 Expression ID: ${id1}`);
104
+ console.log(`Card 2 Expression ID: ${id2}`);
105
+ // Critically, IDs must be unique
106
+ expect(id1).not.toBe(id2);
107
+ expect(id1).not.toBe("expr_card_1"); // Should be rewritten
108
+ expect(id1).toContain("inst");
109
+ // Check registry
110
+ const registryIDs = resolvedIR.template.expressions.map(e => e.id);
111
+ expect(registryIDs).toContain(id1);
112
+ expect(registryIDs).toContain(id2);
113
+ // Ensure both are registered
114
+ expect(registryIDs.length).toBeGreaterThanOrEqual(2);
115
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,51 @@
1
+ import { compileZenSource } from '../index';
2
+ import { InvariantError } from '../errors/compilerError';
3
+ /**
4
+ * Integration Test: Native Error Bridge
5
+ * Triggers an INV005 (TEMPLATE_TAG) error and verifies detailed info.
6
+ */
7
+ async function testNativeErrorBridge() {
8
+ console.log('Testing Native Error Bridge (INV001-INV006)...');
9
+ const source = `
10
+ <script>
11
+ const name = "Zenith";
12
+ </script>
13
+
14
+ <template>
15
+ <div>This is invalid because of the template tag</div>
16
+ </template>
17
+ `;
18
+ try {
19
+ await compileZenSource(source, 'test-invalid.zen');
20
+ throw new Error('Should have thrown an InvariantError for <template> tag');
21
+ }
22
+ catch (e) {
23
+ if (e instanceof InvariantError) {
24
+ console.log('Caught InvariantError:', e.code);
25
+ if (e.code !== 'INV005') {
26
+ throw new Error(`Expected error code INV005, but got ${e.code}`);
27
+ }
28
+ if (e.context !== '<template>') {
29
+ throw new Error(`Expected context "<template>", but got "${e.context}"`);
30
+ }
31
+ if (!e.hints || e.hints.length === 0) {
32
+ throw new Error('Expected hints to be populated');
33
+ }
34
+ if (!e.hints.some(h => h.includes('Zenith component') || h.includes('Card.Header'))) {
35
+ throw new Error('Hints do not contain expected content');
36
+ }
37
+ console.log('✅ Native error bridge test passed');
38
+ }
39
+ else {
40
+ console.error('Unexpected error type:', e);
41
+ throw e;
42
+ }
43
+ }
44
+ }
45
+ // Run tests
46
+ if (import.meta.main) {
47
+ testNativeErrorBridge().catch(e => {
48
+ console.error('❌ Integration test failed:', e.message);
49
+ process.exit(1);
50
+ });
51
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,38 @@
1
+ import { InvariantError, CompilerError } from '../errors/compilerError';
2
+ /**
3
+ * Test cases for the enhanced Error System
4
+ */
5
+ function testErrorStructure() {
6
+ console.log('Testing CompilerError structure...');
7
+ const error = new CompilerError('Test message', 'test.zen', 10, 5, 'COMPILER_ERROR', 'TEST_CONTEXT', ['Hint 1', 'Hint 2']);
8
+ console.assert(error.message.includes('Test message'), 'Message should be preserved');
9
+ console.assert(error.file === 'test.zen', 'File should be preserved');
10
+ console.assert(error.line === 10, 'Line should be preserved');
11
+ console.assert(error.column === 5, 'Column should be preserved');
12
+ console.assert(error.context === 'TEST_CONTEXT', 'Context should be preserved');
13
+ console.assert(error.hints.length === 2, 'Hints should be preserved');
14
+ console.assert(error.hints[0] === 'Hint 1', 'Hint 1 should match');
15
+ console.log('✅ CompilerError structure test passed');
16
+ }
17
+ function testInvariantErrorStructure() {
18
+ console.log('Testing InvariantError structure...');
19
+ const error = new InvariantError('INV001', 'Invariant failed', 'Always be true', 'test.zen', 20, 15, 'INVARIANT_CONTEXT', ['Fix hint']);
20
+ console.assert(error.code === 'INV001', 'Code should be preserved');
21
+ console.assert(error.guarantee === 'Always be true', 'Guarantee should be preserved');
22
+ console.assert(error.errorType === 'InvariantViolation', 'Default errorType should be InvariantViolation');
23
+ console.assert(error.context === 'INVARIANT_CONTEXT', 'Context should be preserved');
24
+ console.assert(error.hints[0] === 'Fix hint', 'Hints should be preserved');
25
+ console.log('✅ InvariantError structure test passed');
26
+ }
27
+ // Run tests
28
+ if (import.meta.main) {
29
+ try {
30
+ testErrorStructure();
31
+ testInvariantErrorStructure();
32
+ console.log('✅ All Error System unit tests passed!');
33
+ }
34
+ catch (e) {
35
+ console.error('❌ Tests failed:', e);
36
+ process.exit(1);
37
+ }
38
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,178 @@
1
+ import { test, expect } from "bun:test";
2
+ import { resolveComponentsInIR } from "../transform/componentResolver";
3
+ // Helper to create a basic ZenIR
4
+ function createIR(templateContent, script = '') {
5
+ return {
6
+ filePath: 'test.zen',
7
+ template: {
8
+ raw: templateContent,
9
+ nodes: [],
10
+ expressions: []
11
+ },
12
+ script: {
13
+ raw: script,
14
+ attributes: {}
15
+ },
16
+ styles: [],
17
+ componentScripts: []
18
+ };
19
+ }
20
+ test("Selective symbol renaming in component expressions", async () => {
21
+ // 1. Mock component metadata
22
+ const counterMeta = {
23
+ name: "Counter",
24
+ hasScript: true,
25
+ script: "let count = 0; function inc() { count++; }",
26
+ expressions: [
27
+ { id: "expr_0", code: "count" },
28
+ { id: "expr_1", code: "Math.floor(count / 2)" }
29
+ ],
30
+ nodes: [
31
+ {
32
+ type: 'element',
33
+ tag: 'button',
34
+ attributes: [{ name: 'onclick', value: { id: 'expr_1', code: 'inc()', location: { line: 1, column: 1 } }, location: { line: 1, column: 1 } }],
35
+ children: [{ type: 'expression', expression: 'expr_0', location: { line: 1, column: 1 } }],
36
+ location: { line: 1, column: 1 }
37
+ }
38
+ ],
39
+ props: [],
40
+ styles: []
41
+ };
42
+ const components = new Map();
43
+ components.set("Counter", counterMeta);
44
+ // 2. Mock page IR with two counter instances
45
+ const pageIR = createIR("<Counter /><Counter />");
46
+ pageIR.template.nodes = [
47
+ { type: 'component', name: 'Counter', attributes: [], children: [], location: { line: 1, column: 1 } },
48
+ { type: 'component', name: 'Counter', attributes: [], children: [], location: { line: 1, column: 10 } }
49
+ ];
50
+ // 3. Resolve components
51
+ const resolvedIR = resolveComponentsInIR(pageIR, components);
52
+ // 4. Verify expressions are renamed
53
+ // We expect 4 expressions (2 per instance)
54
+ expect(resolvedIR.template.expressions.length).toBe(4);
55
+ // Check first instance
56
+ const expr0_inst0 = resolvedIR.template.expressions.find(e => e.id === "expr_0_inst0");
57
+ expect(expr0_inst0?.code).toBe("count_inst0");
58
+ // Check second instance
59
+ const expr0_inst1 = resolvedIR.template.expressions.find(e => e.id === "expr_0_inst1");
60
+ expect(expr0_inst1?.code).toBe("count_inst1");
61
+ // 5. Verify script renaming
62
+ expect(resolvedIR.script?.raw).toContain("let count_inst0 = 0");
63
+ expect(resolvedIR.script?.raw).toContain("function inc_inst0()");
64
+ expect(resolvedIR.script?.raw).toContain("let count_inst1 = 0");
65
+ });
66
+ test("Parent symbols are NOT renamed in component expressions", async () => {
67
+ const compMeta = {
68
+ name: "Hello",
69
+ hasScript: true,
70
+ script: "let local = 'hi';",
71
+ expressions: [
72
+ { id: "expr_0", code: "local + globalVar" }
73
+ ],
74
+ nodes: [],
75
+ props: [],
76
+ styles: []
77
+ };
78
+ const components = new Map();
79
+ components.set("Hello", compMeta);
80
+ const pageIR = createIR("<Hello />");
81
+ pageIR.template.nodes = [{ type: 'component', name: 'Hello', attributes: [], children: [], location: { line: 1, column: 1 } }];
82
+ const resIR = resolveComponentsInIR(pageIR, components);
83
+ const expr = resIR.template.expressions[0];
84
+ // 'local' should be renamed, 'globalVar' should NOT
85
+ expect(expr?.code).toContain("_inst");
86
+ expect(expr?.code).toContain("globalVar");
87
+ expect(expr?.code).not.toContain("globalVar_inst");
88
+ });
89
+ test("Macro Prop Substitution", async () => {
90
+ const compMeta = {
91
+ name: "Welcome",
92
+ hasScript: true,
93
+ script: "console.log(title)",
94
+ expressions: [
95
+ { id: "expr_0", code: "title" }
96
+ ],
97
+ nodes: [],
98
+ props: ["title"],
99
+ styles: []
100
+ };
101
+ const components = new Map();
102
+ components.set("Welcome", compMeta);
103
+ const pageIR = createIR("<Welcome title={pageTitle} />");
104
+ pageIR.template.nodes = [{
105
+ type: 'component',
106
+ name: 'Welcome',
107
+ attributes: [{
108
+ name: 'title',
109
+ value: { id: 'expr_P', code: 'pageTitle', location: { line: 1, column: 1 } },
110
+ location: { line: 1, column: 1 }
111
+ }],
112
+ children: [],
113
+ location: { line: 1, column: 1 }
114
+ }];
115
+ const resolvedIR = resolveComponentsInIR(pageIR, components);
116
+ // Expression should be substituted with parent code
117
+ const expr = resolvedIR.template.expressions.find(e => e.id === "expr_0_inst0");
118
+ expect(expr?.code).toBe("(pageTitle)");
119
+ // Script should also be substituted
120
+ expect(resolvedIR.script?.raw).toContain("console.log((pageTitle))");
121
+ });
122
+ test("Event Handler Renaming in Template", async () => {
123
+ const compMeta = {
124
+ name: "Button",
125
+ hasScript: true,
126
+ script: "function handleClick() { console.log('clicked'); }",
127
+ expressions: [],
128
+ nodes: [
129
+ {
130
+ type: 'element',
131
+ tag: 'button',
132
+ attributes: [
133
+ { name: 'onclick', value: 'handleClick', location: { line: 1, column: 1 } }
134
+ ],
135
+ children: [],
136
+ location: { line: 1, column: 1 }
137
+ }
138
+ ],
139
+ props: [],
140
+ styles: []
141
+ };
142
+ const components = new Map();
143
+ components.set("Button", compMeta);
144
+ const pageIR = createIR("<Button />");
145
+ pageIR.template.nodes = [{ type: 'component', name: 'Button', attributes: [], children: [], location: { line: 1, column: 1 } }];
146
+ const resolvedIR = resolveComponentsInIR(pageIR, components);
147
+ // The button's onclick should be renamed
148
+ const button = resolvedIR.template.nodes[0];
149
+ expect(button.attributes[0].value).toBe("handleClick_inst0");
150
+ });
151
+ test("Multi-instance isolation and props.prefix substitution", async () => {
152
+ const compMeta = {
153
+ name: "Box",
154
+ hasScript: true,
155
+ script: "const val = signal(props.initial);",
156
+ expressions: [{ id: "expr_0", code: "val.value + props.label" }],
157
+ nodes: [],
158
+ props: ["initial", "label"],
159
+ styles: []
160
+ };
161
+ const components = new Map();
162
+ components.set("Box", compMeta);
163
+ const pageIR = createIR("<Box initial={1} label='A' /><Box initial={10} label='B' />");
164
+ pageIR.template.nodes = [
165
+ { type: 'component', name: 'Box', attributes: [{ name: 'initial', value: { id: 'expr_P1', code: '1' } }, { name: 'label', value: 'A' }], children: [], location: { line: 1, column: 1 } },
166
+ { type: 'component', name: 'Box', attributes: [{ name: 'initial', value: { id: 'expr_P2', code: '10' } }, { name: 'label', value: 'B' }], children: [], location: { line: 1, column: 30 } }
167
+ ];
168
+ const resolvedIR = resolveComponentsInIR(pageIR, components);
169
+ // Verify first instance
170
+ const script = resolvedIR.script?.raw || "";
171
+ expect(script).toContain("const val_inst0 = signal((1))");
172
+ const expr0 = resolvedIR.template.expressions.find(e => e.id === "expr_0_inst0");
173
+ expect(expr0?.code).toBe("val_inst0.value + \"A\"");
174
+ // Verify second instance
175
+ expect(script).toContain("const val_inst1 = signal((10))");
176
+ const expr1 = resolvedIR.template.expressions.find(e => e.id === "expr_0_inst1");
177
+ expect(expr1?.code).toBe("val_inst1.value + \"B\"");
178
+ });
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Test Cases for Expression Validation
3
+ *
4
+ * Phase 8/9/10: Tests that invalid expressions fail the build
5
+ */
6
+ export {};
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ /**
3
+ * Test Cases for Expression Validation
4
+ *
5
+ * Phase 8/9/10: Tests that invalid expressions fail the build
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ const validateExpressions_1 = require("../validate/validateExpressions");
9
+ /**
10
+ * Test valid expressions
11
+ */
12
+ function testValidExpressions() {
13
+ const validExpressions = [
14
+ {
15
+ id: 'expr_0',
16
+ code: 'user.name',
17
+ location: { line: 10, column: 5 }
18
+ },
19
+ {
20
+ id: 'expr_1',
21
+ code: 'count + 1',
22
+ location: { line: 11, column: 8 }
23
+ },
24
+ {
25
+ id: 'expr_2',
26
+ code: 'isActive ? "on" : "off"',
27
+ location: { line: 12, column: 12 }
28
+ }
29
+ ];
30
+ const result = (0, validateExpressions_1.validateExpressions)(validExpressions, 'test.zen');
31
+ console.assert(result.valid === true, 'Valid expressions should pass validation');
32
+ console.assert(result.errors.length === 0, 'Valid expressions should have no errors');
33
+ console.log('✅ Valid expressions test passed');
34
+ }
35
+ /**
36
+ * Test invalid expressions
37
+ */
38
+ function testInvalidExpressions() {
39
+ const invalidExpressions = [
40
+ {
41
+ id: 'expr_0',
42
+ code: 'user.name}', // Mismatched brace
43
+ location: { line: 10, column: 5 }
44
+ }
45
+ ];
46
+ const result = (0, validateExpressions_1.validateExpressions)(invalidExpressions, 'test.zen');
47
+ console.assert(result.valid === false, 'Invalid expressions should fail validation');
48
+ console.assert(result.errors.length > 0, 'Invalid expressions should have errors');
49
+ console.log('✅ Invalid expressions test passed');
50
+ }
51
+ /**
52
+ * Test unsafe code detection
53
+ */
54
+ function testUnsafeCode() {
55
+ const unsafeExpressions = [
56
+ {
57
+ id: 'expr_0',
58
+ code: 'eval("alert(1)")',
59
+ location: { line: 10, column: 5 }
60
+ }
61
+ ];
62
+ const result = (0, validateExpressions_1.validateExpressions)(unsafeExpressions, 'test.zen');
63
+ console.assert(result.valid === false, 'Unsafe code should fail validation');
64
+ console.assert(result.errors.length > 0, 'Unsafe code should have errors');
65
+ console.log('✅ Unsafe code detection test passed');
66
+ }
67
+ /**
68
+ * Test validateExpressionsOrThrow
69
+ */
70
+ function testThrowOnInvalid() {
71
+ const invalidExpressions = [
72
+ {
73
+ id: 'expr_0',
74
+ code: 'user.name}', // Mismatched brace
75
+ location: { line: 10, column: 5 }
76
+ }
77
+ ];
78
+ try {
79
+ (0, validateExpressions_1.validateExpressionsOrThrow)(invalidExpressions, 'test.zen');
80
+ console.assert(false, 'Should have thrown on invalid expressions');
81
+ }
82
+ catch (error) {
83
+ console.assert(error instanceof Error, 'Should throw Error');
84
+ console.log('✅ Throw on invalid expressions test passed');
85
+ }
86
+ }
87
+ // Run tests
88
+ if (require.main === module) {
89
+ console.log('Running validation tests...');
90
+ testValidExpressions();
91
+ testInvalidExpressions();
92
+ testUnsafeCode();
93
+ testThrowOnInvalid();
94
+ console.log('✅ All validation tests passed!');
95
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Expression Classification
3
+ *
4
+ * Analyzes expression code to determine output type for structural lowering.
5
+ *
6
+ * JSX expressions are allowed if — and only if — the compiler can statically
7
+ * enumerate all possible DOM shapes and lower them at compile time.
8
+ */
9
+ /**
10
+ * Expression output types
11
+ *
12
+ * - primitive: string, number, boolean → text binding
13
+ * - conditional: cond ? <A /> : <B /> → ConditionalFragmentNode
14
+ * - optional: cond && <A /> → OptionalFragmentNode
15
+ * - loop: arr.map(i => <JSX />) → LoopFragmentNode
16
+ * - fragment: <A /> or <><A /><B /></> → inline fragment
17
+ * - unknown: cannot be statically determined → COMPILE ERROR
18
+ */
19
+ export type ExpressionOutputType = 'primitive' | 'conditional' | 'optional' | 'loop' | 'fragment' | 'unknown';
20
+ /**
21
+ * Classification result with extracted metadata
22
+ */
23
+ export interface ExpressionClassification {
24
+ type: ExpressionOutputType;
25
+ condition?: string;
26
+ consequent?: string;
27
+ alternate?: string;
28
+ optionalCondition?: string;
29
+ optionalFragment?: string;
30
+ loopSource?: string;
31
+ loopItemVar?: string;
32
+ loopIndexVar?: string;
33
+ loopBody?: string;
34
+ fragmentCode?: string;
35
+ }
36
+ /**
37
+ * Classify expression output type
38
+ *
39
+ * @param code - The expression code to classify
40
+ * @returns Classification result with metadata
41
+ */
42
+ export declare function classifyExpression(code: string): ExpressionClassification;
43
+ /**
44
+ * Check if an expression type requires structural lowering
45
+ */
46
+ export declare function requiresStructuralLowering(type: ExpressionOutputType): boolean;