@zenithbuild/core 0.4.6 → 0.5.0-beta.2.1

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 (109) hide show
  1. package/CORE_CONTRACT.md +143 -0
  2. package/README.md +11 -31
  3. package/bin/zenith.js +68 -0
  4. package/package.json +40 -52
  5. package/src/config.js +134 -0
  6. package/src/core-template.js +30 -0
  7. package/src/errors.js +54 -0
  8. package/src/guards.js +61 -0
  9. package/src/hash.js +52 -0
  10. package/src/index.js +26 -0
  11. package/src/ir/index.js +1 -0
  12. package/src/order.js +69 -0
  13. package/src/path.js +131 -0
  14. package/src/schema.js +28 -0
  15. package/src/version.js +67 -0
  16. package/bin/zen-build.ts +0 -2
  17. package/bin/zen-dev.ts +0 -2
  18. package/bin/zen-preview.ts +0 -2
  19. package/bin/zenith.ts +0 -2
  20. package/cli/commands/add.ts +0 -37
  21. package/cli/commands/build.ts +0 -37
  22. package/cli/commands/create.ts +0 -702
  23. package/cli/commands/dev.ts +0 -335
  24. package/cli/commands/index.ts +0 -112
  25. package/cli/commands/preview.ts +0 -62
  26. package/cli/commands/remove.ts +0 -33
  27. package/cli/index.ts +0 -10
  28. package/cli/main.ts +0 -101
  29. package/cli/utils/branding.ts +0 -178
  30. package/cli/utils/content.ts +0 -112
  31. package/cli/utils/logger.ts +0 -46
  32. package/cli/utils/plugin-manager.ts +0 -114
  33. package/cli/utils/project.ts +0 -77
  34. package/compiler/README.md +0 -380
  35. package/compiler/build-analyzer.ts +0 -122
  36. package/compiler/css/index.ts +0 -317
  37. package/compiler/discovery/componentDiscovery.ts +0 -174
  38. package/compiler/discovery/layouts.ts +0 -61
  39. package/compiler/errors/compilerError.ts +0 -56
  40. package/compiler/finalize/finalizeOutput.ts +0 -192
  41. package/compiler/finalize/generateFinalBundle.ts +0 -82
  42. package/compiler/index.ts +0 -81
  43. package/compiler/ir/types.ts +0 -150
  44. package/compiler/output/types.ts +0 -34
  45. package/compiler/parse/detectMapExpressions.ts +0 -102
  46. package/compiler/parse/parseScript.ts +0 -46
  47. package/compiler/parse/parseTemplate.ts +0 -591
  48. package/compiler/parse/parseZenFile.ts +0 -66
  49. package/compiler/parse/scriptAnalysis.ts +0 -83
  50. package/compiler/parse/trackLoopContext.ts +0 -82
  51. package/compiler/runtime/dataExposure.ts +0 -317
  52. package/compiler/runtime/generateDOM.ts +0 -246
  53. package/compiler/runtime/generateHydrationBundle.ts +0 -407
  54. package/compiler/runtime/hydration.ts +0 -309
  55. package/compiler/runtime/navigation.ts +0 -432
  56. package/compiler/runtime/thinRuntime.ts +0 -160
  57. package/compiler/runtime/transformIR.ts +0 -343
  58. package/compiler/runtime/wrapExpression.ts +0 -95
  59. package/compiler/runtime/wrapExpressionWithLoop.ts +0 -83
  60. package/compiler/spa-build.ts +0 -917
  61. package/compiler/ssg-build.ts +0 -422
  62. package/compiler/test/validate-test.ts +0 -104
  63. package/compiler/transform/classifyExpression.ts +0 -444
  64. package/compiler/transform/componentResolver.ts +0 -289
  65. package/compiler/transform/expressionTransformer.ts +0 -385
  66. package/compiler/transform/fragmentLowering.ts +0 -634
  67. package/compiler/transform/generateBindings.ts +0 -47
  68. package/compiler/transform/generateHTML.ts +0 -28
  69. package/compiler/transform/layoutProcessor.ts +0 -132
  70. package/compiler/transform/slotResolver.ts +0 -292
  71. package/compiler/transform/transformNode.ts +0 -126
  72. package/compiler/transform/transformTemplate.ts +0 -38
  73. package/compiler/validate/invariants.ts +0 -292
  74. package/compiler/validate/validateExpressions.ts +0 -168
  75. package/core/config/index.ts +0 -16
  76. package/core/config/loader.ts +0 -69
  77. package/core/config/types.ts +0 -89
  78. package/core/index.ts +0 -135
  79. package/core/lifecycle/index.ts +0 -49
  80. package/core/lifecycle/zen-mount.ts +0 -182
  81. package/core/lifecycle/zen-unmount.ts +0 -88
  82. package/core/plugins/index.ts +0 -7
  83. package/core/plugins/registry.ts +0 -81
  84. package/core/reactivity/index.ts +0 -54
  85. package/core/reactivity/tracking.ts +0 -167
  86. package/core/reactivity/zen-batch.ts +0 -57
  87. package/core/reactivity/zen-effect.ts +0 -139
  88. package/core/reactivity/zen-memo.ts +0 -146
  89. package/core/reactivity/zen-ref.ts +0 -52
  90. package/core/reactivity/zen-signal.ts +0 -121
  91. package/core/reactivity/zen-state.ts +0 -180
  92. package/core/reactivity/zen-untrack.ts +0 -44
  93. package/dist/cli.js +0 -11653
  94. package/dist/zen-build.js +0 -15388
  95. package/dist/zen-dev.js +0 -15388
  96. package/dist/zen-preview.js +0 -15388
  97. package/dist/zenith.js +0 -15388
  98. package/router/index.ts +0 -76
  99. package/router/manifest.ts +0 -314
  100. package/router/navigation/ZenLink.zen +0 -231
  101. package/router/navigation/index.ts +0 -78
  102. package/router/navigation/zen-link.ts +0 -584
  103. package/router/runtime.ts +0 -458
  104. package/router/types.ts +0 -168
  105. package/runtime/build.ts +0 -17
  106. package/runtime/bundle-generator.ts +0 -800
  107. package/runtime/client-runtime.ts +0 -549
  108. package/runtime/serve.ts +0 -93
  109. package/tsconfig.json +0 -28
@@ -1,192 +0,0 @@
1
- /**
2
- * Finalize Output
3
- *
4
- * Phase 8/9/10: Generate final compiled HTML + JS output with hydration markers
5
- *
6
- * Ensures:
7
- * - All expressions are replaced with hydration markers
8
- * - HTML contains no raw {expression} syntax
9
- * - JS runtime is ready for browser execution
10
- * - Hydration markers are correctly placed
11
- */
12
-
13
- import type { CompiledTemplate } from '../output/types'
14
- import type { ZenIR } from '../ir/types'
15
- import { transformIR, type RuntimeCode } from '../runtime/transformIR'
16
- import { validateExpressionsOrThrow } from '../validate/validateExpressions'
17
-
18
- /**
19
- * Finalized output ready for browser
20
- */
21
- export interface FinalizedOutput {
22
- html: string
23
- js: string
24
- styles: string[]
25
- hasErrors: boolean
26
- errors: string[]
27
- }
28
-
29
- /**
30
- * Finalize compiler output
31
- *
32
- * This is the final step that ensures:
33
- * 1. All expressions are validated
34
- * 2. HTML contains no raw expressions
35
- * 3. JS runtime is generated
36
- * 4. Output is ready for browser
37
- *
38
- * @param ir - Intermediate representation
39
- * @param compiled - Compiled template from Phase 2
40
- * @returns Finalized output
41
- */
42
- export function finalizeOutput(
43
- ir: ZenIR,
44
- compiled: CompiledTemplate
45
- ): FinalizedOutput {
46
- const errors: string[] = []
47
-
48
- // 1. Validate all expressions (Phase 8/9/10 requirement)
49
- try {
50
- validateExpressionsOrThrow(ir.template.expressions, ir.filePath)
51
- } catch (error: any) {
52
- if (error instanceof Error) {
53
- errors.push(error.message)
54
- return {
55
- html: '',
56
- js: '',
57
- styles: [],
58
- hasErrors: true,
59
- errors
60
- }
61
- }
62
- }
63
-
64
- // 2. Verify HTML contains no raw expressions
65
- const htmlErrors = verifyNoRawExpressions(compiled.html, ir.filePath)
66
- if (htmlErrors.length > 0) {
67
- errors.push(...htmlErrors)
68
- return {
69
- html: '',
70
- js: '',
71
- styles: [],
72
- hasErrors: true,
73
- errors
74
- }
75
- }
76
-
77
- // 3. Generate runtime code
78
- let runtimeCode: RuntimeCode
79
- try {
80
- runtimeCode = transformIR(ir)
81
- } catch (error: any) {
82
- errors.push(`Runtime generation failed: ${error.message}`)
83
- return {
84
- html: '',
85
- js: '',
86
- styles: [],
87
- hasErrors: true,
88
- errors
89
- }
90
- }
91
-
92
- // 4. Combine HTML and JS
93
- const finalHTML = compiled.html
94
- const finalJS = runtimeCode.bundle
95
-
96
- return {
97
- html: finalHTML,
98
- js: finalJS,
99
- styles: compiled.styles,
100
- hasErrors: false,
101
- errors: []
102
- }
103
- }
104
-
105
- /**
106
- * Verify HTML contains no raw {expression} syntax
107
- *
108
- * This is a critical check - browser must never see raw expressions
109
- *
110
- * Excludes:
111
- * - Content inside <pre>, <code> tags (display code samples)
112
- * - Content that looks like HTML tags (from entity decoding)
113
- * - Comments
114
- * - Data attributes
115
- */
116
- function verifyNoRawExpressions(html: string, filePath: string): string[] {
117
- const errors: string[] = []
118
-
119
- // Remove content inside <pre> and <code> tags before checking
120
- // These are code samples that may contain { } legitimately
121
- let htmlToCheck = html
122
- .replace(/<pre[^>]*>[\s\S]*?<\/pre>/gi, '')
123
- .replace(/<code[^>]*>[\s\S]*?<\/code>/gi, '')
124
-
125
- // Check for raw {expression} patterns (not data-zen-* attributes)
126
- // Allow data-zen-text, data-zen-attr-* but not raw { }
127
- const rawExpressionPattern = /\{[^}]*\}/g
128
- const matches = htmlToCheck.match(rawExpressionPattern)
129
-
130
- if (matches && matches.length > 0) {
131
- // Filter out false positives
132
- const actualExpressions = matches.filter(match => {
133
- // Exclude if it's in a comment
134
- if (html.includes(`<!--${match}`) || html.includes(`${match}-->`)) {
135
- return false
136
- }
137
- // Exclude if it's in a data attribute value (already processed)
138
- if (match.includes('data-zen-')) {
139
- return false
140
- }
141
- // Exclude if it contains HTML tags (likely from entity decoding in display content)
142
- // Real expressions don't start with < inside braces
143
- if (match.match(/^\{[\s]*</)) {
144
- return false
145
- }
146
- // Exclude if it looks like display content containing HTML (spans, divs, etc)
147
- if (/<[a-zA-Z]/.test(match)) {
148
- return false
149
- }
150
- // Exclude CSS-like content (common in style attributes)
151
- if (match.includes(';') && match.includes(':')) {
152
- return false
153
- }
154
- // Exclude if it's a single closing tag pattern (from multiline display)
155
- if (/^\{[\s]*<\//.test(match)) {
156
- return false
157
- }
158
- // This looks like a raw expression
159
- return true
160
- })
161
-
162
- if (actualExpressions.length > 0) {
163
- errors.push(
164
- `HTML contains raw expressions that were not compiled: ${actualExpressions.join(', ')}\n` +
165
- `File: ${filePath}\n` +
166
- `All expressions must be replaced with hydration markers (data-zen-text, data-zen-attr-*)`
167
- )
168
- }
169
- }
170
-
171
- return errors
172
- }
173
-
174
- /**
175
- * Generate final output with error handling
176
- *
177
- * Throws if validation fails (build must fail on errors)
178
- */
179
- export function finalizeOutputOrThrow(
180
- ir: ZenIR,
181
- compiled: CompiledTemplate
182
- ): FinalizedOutput {
183
- const output = finalizeOutput(ir, compiled)
184
-
185
- if (output.hasErrors) {
186
- const errorMessage = output.errors.join('\n\n')
187
- throw new Error(`Compilation failed:\n\n${errorMessage}`)
188
- }
189
-
190
- return output
191
- }
192
-
@@ -1,82 +0,0 @@
1
- /**
2
- * Generate Final Bundle
3
- *
4
- * Phase 8/9/10: Generate final browser-ready bundle
5
- *
6
- * Combines:
7
- * - Compiled HTML
8
- * - Runtime JS
9
- * - Expression functions
10
- * - Event bindings
11
- * - Style injection
12
- */
13
-
14
- import type { FinalizedOutput } from './finalizeOutput'
15
- import type { RuntimeCode } from '../runtime/transformIR'
16
-
17
- /**
18
- * Generate final bundle code
19
- *
20
- * This is the complete JavaScript bundle that will execute in the browser.
21
- * All expressions are pre-compiled - no template parsing at runtime.
22
- */
23
- export function generateFinalBundle(finalized: FinalizedOutput): string {
24
- return `// Zenith Compiled Bundle (Phase 8/9/10)
25
- // Generated at compile time - no .zen parsing in browser
26
- // All expressions are pre-compiled - deterministic output
27
-
28
- ${finalized.js}
29
-
30
- // Bundle complete - ready for browser execution
31
- `
32
- }
33
-
34
- /**
35
- * Generate HTML with inline script
36
- */
37
- export function generateHTMLWithScript(
38
- html: string,
39
- jsBundle: string,
40
- styles: string[]
41
- ): string {
42
- // Inject styles as <style> tags
43
- const styleTags = styles.map(style => `<style>${escapeHTML(style)}</style>`).join('\n')
44
-
45
- // Inject JS bundle as inline script
46
- const scriptTag = `<script>${jsBundle}</script>`
47
-
48
- // Find </head> or <body> to inject styles
49
- // Find </body> to inject script
50
- let result = html
51
-
52
- if (styleTags) {
53
- if (result.includes('</head>')) {
54
- result = result.replace('</head>', `${styleTags}\n</head>`)
55
- } else if (result.includes('<body')) {
56
- result = result.replace('<body', `${styleTags}\n<body`)
57
- }
58
- }
59
-
60
- if (scriptTag) {
61
- if (result.includes('</body>')) {
62
- result = result.replace('</body>', `${scriptTag}\n</body>`)
63
- } else {
64
- result += scriptTag
65
- }
66
- }
67
-
68
- return result
69
- }
70
-
71
- /**
72
- * Escape HTML for safe embedding
73
- */
74
- function escapeHTML(str: string): string {
75
- return str
76
- .replace(/&/g, '&amp;')
77
- .replace(/</g, '&lt;')
78
- .replace(/>/g, '&gt;')
79
- .replace(/"/g, '&quot;')
80
- .replace(/'/g, '&#039;')
81
- }
82
-
package/compiler/index.ts DELETED
@@ -1,81 +0,0 @@
1
- import { readFileSync } from 'fs'
2
- import { parseTemplate } from './parse/parseTemplate'
3
- import { parseScript } from './parse/parseScript'
4
- import { transformTemplate } from './transform/transformTemplate'
5
- import { finalizeOutputOrThrow } from './finalize/finalizeOutput'
6
- import { validateInvariants } from './validate/invariants'
7
- import { InvariantError } from './errors/compilerError'
8
- import type { ZenIR, StyleIR } from './ir/types'
9
- import type { CompiledTemplate } from './output/types'
10
- import type { FinalizedOutput } from './finalize/finalizeOutput'
11
-
12
- /**
13
- * Compile a .zen file into IR and CompiledTemplate
14
- */
15
- export function compileZen(filePath: string): {
16
- ir: ZenIR
17
- compiled: CompiledTemplate
18
- finalized?: FinalizedOutput
19
- } {
20
- const source = readFileSync(filePath, 'utf-8')
21
- return compileZenSource(source, filePath)
22
- }
23
-
24
- /**
25
- * Compile Zen source string into IR and CompiledTemplate
26
- */
27
- export function compileZenSource(
28
- source: string,
29
- filePath: string,
30
- options?: {
31
- componentsDir?: string
32
- }
33
- ): {
34
- ir: ZenIR
35
- compiled: CompiledTemplate
36
- finalized?: FinalizedOutput
37
- } {
38
- // Parse template
39
- const template = parseTemplate(source, filePath)
40
-
41
- // Parse script
42
- const script = parseScript(source)
43
-
44
- // Parse styles
45
- const styleRegex = /\u003cstyle[^\u003e]*\u003e([\s\S]*?)\u003c\/style\u003e/gi
46
- const styles: StyleIR[] = []
47
- let match
48
- while ((match = styleRegex.exec(source)) !== null) {
49
- if (match[1]) styles.push({ raw: match[1].trim() })
50
- }
51
-
52
- let ir: ZenIR = {
53
- filePath,
54
- template,
55
- script,
56
- styles
57
- }
58
-
59
- // Resolve components if components directory is provided
60
- if (options?.componentsDir) {
61
- const { discoverComponents } = require('./discovery/componentDiscovery')
62
- const { resolveComponentsInIR } = require('./transform/componentResolver')
63
-
64
- // Component resolution may throw InvariantError — let it propagate
65
- const components = discoverComponents(options.componentsDir)
66
- ir = resolveComponentsInIR(ir, components)
67
- }
68
-
69
- // Validate all compiler invariants after resolution
70
- // Throws InvariantError if any invariant is violated
71
- validateInvariants(ir, filePath)
72
-
73
- const compiled = transformTemplate(ir)
74
-
75
- try {
76
- const finalized = finalizeOutputOrThrow(ir, compiled)
77
- return { ir, compiled, finalized }
78
- } catch (error: any) {
79
- throw new Error(`Failed to finalize output for ${filePath}:\\n${error.message}`)
80
- }
81
- }
@@ -1,150 +0,0 @@
1
- /**
2
- * Zenith Intermediate Representation (IR)
3
- *
4
- * Phase 1: Parse & Extract
5
- * This IR represents the parsed structure of a .zen file
6
- * without any runtime execution or transformation.
7
- */
8
-
9
- export type ZenIR = {
10
- filePath: string
11
- template: TemplateIR
12
- script: ScriptIR | null
13
- styles: StyleIR[]
14
- }
15
-
16
- export type TemplateIR = {
17
- raw: string
18
- nodes: TemplateNode[]
19
- expressions: ExpressionIR[]
20
- }
21
-
22
- export type TemplateNode =
23
- | ElementNode
24
- | TextNode
25
- | ExpressionNode
26
- | ComponentNode
27
- | ConditionalFragmentNode // JSX ternary: {cond ? <A /> : <B />}
28
- | OptionalFragmentNode // JSX logical AND: {cond && <A />}
29
- | LoopFragmentNode // JSX map: {items.map(i => <li>...</li>)}
30
-
31
- export type ElementNode = {
32
- type: 'element'
33
- tag: string
34
- attributes: AttributeIR[]
35
- children: TemplateNode[]
36
- location: SourceLocation
37
- loopContext?: LoopContext // Phase 7: Inherited loop context from parent map expressions
38
- }
39
-
40
- export type ComponentNode = {
41
- type: 'component'
42
- name: string
43
- attributes: AttributeIR[]
44
- children: TemplateNode[]
45
- location: SourceLocation
46
- loopContext?: LoopContext
47
- }
48
-
49
- export type TextNode = {
50
- type: 'text'
51
- value: string
52
- location: SourceLocation
53
- }
54
-
55
- export type ExpressionNode = {
56
- type: 'expression'
57
- expression: string
58
- location: SourceLocation
59
- loopContext?: LoopContext // Phase 7: Loop context for expressions inside map iterations
60
- }
61
-
62
- /**
63
- * Conditional Fragment Node
64
- *
65
- * Represents ternary expressions with JSX branches: {cond ? <A /> : <B />}
66
- *
67
- * BOTH branches are compiled at compile time.
68
- * Runtime toggles visibility — never creates DOM.
69
- */
70
- export type ConditionalFragmentNode = {
71
- type: 'conditional-fragment'
72
- condition: string // The condition expression code
73
- consequent: TemplateNode[] // Precompiled "true" branch
74
- alternate: TemplateNode[] // Precompiled "false" branch
75
- location: SourceLocation
76
- loopContext?: LoopContext
77
- }
78
-
79
- /**
80
- * Optional Fragment Node
81
- *
82
- * Represents logical AND expressions with JSX: {cond && <A />}
83
- *
84
- * Fragment is compiled at compile time.
85
- * Runtime toggles mount/unmount based on condition.
86
- */
87
- export type OptionalFragmentNode = {
88
- type: 'optional-fragment'
89
- condition: string // The condition expression code
90
- fragment: TemplateNode[] // Precompiled fragment
91
- location: SourceLocation
92
- loopContext?: LoopContext
93
- }
94
-
95
- /**
96
- * Loop Fragment Node
97
- *
98
- * Represents .map() expressions with JSX body: {items.map(i => <li>...</li>)}
99
- *
100
- * Desugars to @for loop semantics at compile time.
101
- * Body is compiled once, instantiated per item at runtime.
102
- * Node identity is compiler-owned via stable keys.
103
- */
104
- export type LoopFragmentNode = {
105
- type: 'loop-fragment'
106
- source: string // Array expression (e.g., 'items')
107
- itemVar: string // Loop variable (e.g., 'item')
108
- indexVar?: string // Optional index variable
109
- body: TemplateNode[] // Precompiled loop body template
110
- location: SourceLocation
111
- loopContext: LoopContext // Extended with this loop's variables
112
- }
113
-
114
- export type AttributeIR = {
115
- name: string
116
- value: string | ExpressionIR
117
- location: SourceLocation
118
- loopContext?: LoopContext // Phase 7: Loop context for expressions inside map iterations
119
- }
120
-
121
- /**
122
- * Loop context for expressions inside map iterations
123
- * Phase 7: Tracks loop variables (e.g., todo, index) for expressions inside .map() calls
124
- */
125
- export type LoopContext = {
126
- variables: string[] // e.g., ['todo', 'index'] for todoItems.map((todo, index) => ...)
127
- mapSource?: string // The array being mapped, e.g., 'todoItems'
128
- }
129
-
130
- export type ExpressionIR = {
131
- id: string
132
- code: string
133
- location: SourceLocation
134
- }
135
-
136
- export type ScriptIR = {
137
- raw: string
138
- attributes: Record<string, string>
139
- }
140
-
141
- export type StyleIR = {
142
- raw: string
143
- }
144
-
145
- export type SourceLocation = {
146
- line: number
147
- column: number
148
- }
149
-
150
-
@@ -1,34 +0,0 @@
1
- /**
2
- * Compiled Template Output Types
3
- *
4
- * Phase 2: Transform IR → Static HTML + Runtime Bindings
5
- */
6
-
7
- export type CompiledTemplate = {
8
- html: string
9
- bindings: Binding[]
10
- scripts: string | null
11
- styles: string[]
12
- }
13
-
14
- export type Binding = {
15
- id: string
16
- type: 'text' | 'attribute'
17
- target: string // e.g., "data-zen-text" or "class" for attribute bindings
18
- expression: string // The original expression code
19
- location?: {
20
- line: number
21
- column: number
22
- }
23
- loopContext?: LoopContext // Phase 7: Loop context for expressions inside map iterations
24
- }
25
-
26
- /**
27
- * Loop context for expressions inside map iterations
28
- * Phase 7: Tracks loop variables for runtime setter generation
29
- */
30
- export type LoopContext = {
31
- variables: string[] // e.g., ['todo', 'index']
32
- mapSource?: string // The array being mapped, e.g., 'todoItems'
33
- }
34
-
@@ -1,102 +0,0 @@
1
- /**
2
- * Map Expression Detection
3
- *
4
- * Phase 7: Detects .map() expressions and extracts loop context information
5
- *
6
- * This module analyzes expression code to detect map expressions like:
7
- * - todoItems.map(todo => ...)
8
- * - notifications.map((n, index) => ...)
9
- *
10
- * It extracts:
11
- * - The array source (todoItems, notifications)
12
- * - Loop variable names (todo, n, index)
13
- * - The map body/template
14
- */
15
-
16
- import type { ExpressionIR } from '../ir/types'
17
-
18
- /**
19
- * Detected map expression information
20
- */
21
- export interface MapExpressionInfo {
22
- isMap: boolean
23
- arraySource?: string // e.g., 'todoItems'
24
- itemVariable?: string // e.g., 'todo'
25
- indexVariable?: string // e.g., 'index'
26
- mapBody?: string // The template/body inside the map
27
- fullExpression?: string // The full expression code
28
- }
29
-
30
- /**
31
- * Detect if an expression is a map expression and extract loop context
32
- *
33
- * Patterns detected:
34
- * - arraySource.map(item => body)
35
- * - arraySource.map((item, index) => body)
36
- * - arraySource.map(item => (body))
37
- */
38
- export function detectMapExpression(expr: ExpressionIR): MapExpressionInfo {
39
- const { code } = expr
40
-
41
- // Pattern: arraySource.map(item => body)
42
- // Pattern: arraySource.map((item, index) => body)
43
- // Pattern: arraySource.map(item => (body))
44
- const mapPattern = /^(.+?)\.\s*map\s*\(\s*\(?([^)=,\s]+)(?:\s*,\s*([^)=,\s]+))?\s*\)?\s*=>\s*(.+?)\)?$/s
45
-
46
- const match = code.match(mapPattern)
47
- if (!match) {
48
- return { isMap: false }
49
- }
50
-
51
- const arraySource = match[1]?.trim()
52
- const itemVariable = match[2]?.trim()
53
- const indexVariable = match[3]?.trim()
54
- const mapBody = match[4]?.trim()
55
-
56
- if (!arraySource || !itemVariable || !mapBody) {
57
- return { isMap: false }
58
- }
59
-
60
- return {
61
- isMap: true,
62
- arraySource,
63
- itemVariable,
64
- indexVariable,
65
- mapBody,
66
- fullExpression: code
67
- }
68
- }
69
-
70
- /**
71
- * Extract loop variables from a map expression
72
- * Returns array of variable names in order: [itemVariable, indexVariable?]
73
- */
74
- export function extractLoopVariables(mapInfo: MapExpressionInfo): string[] {
75
- if (!mapInfo.isMap || !mapInfo.itemVariable) {
76
- return []
77
- }
78
-
79
- const vars = [mapInfo.itemVariable]
80
- if (mapInfo.indexVariable) {
81
- vars.push(mapInfo.indexVariable)
82
- }
83
-
84
- return vars
85
- }
86
-
87
- /**
88
- * Check if an expression references a loop variable
89
- * Used to determine if an expression needs loop context
90
- */
91
- export function referencesLoopVariable(exprCode: string, loopVars: string[]): boolean {
92
- for (const loopVar of loopVars) {
93
- // Match variable references: loopVar.property, loopVar, etc.
94
- // Use word boundaries to avoid partial matches
95
- const pattern = new RegExp(`\\b${loopVar}\\b`)
96
- if (pattern.test(exprCode)) {
97
- return true
98
- }
99
- }
100
- return false
101
- }
102
-
@@ -1,46 +0,0 @@
1
- /**
2
- * Script Parser
3
- *
4
- * Extracts <script> blocks from .zen files
5
- * Phase 1: Only extracts raw content, no evaluation
6
- */
7
-
8
- import type { ScriptIR } from '../ir/types'
9
-
10
- export function parseScript(html: string): ScriptIR | null {
11
- const scripts: string[] = []
12
- const attributes: Record<string, string> = {}
13
-
14
- const scriptRegex = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi
15
- let match
16
-
17
- while ((match = scriptRegex.exec(html)) !== null) {
18
- const attrString = match[1] || ''
19
- const content = match[2] || ''
20
-
21
- // Parse attributes
22
- const attrRegex = /([a-z0-9-]+)(?:=(?:"([^"]*)"|'([^']*)'|([^>\s]+)))?/gi
23
- let attrMatch
24
- while ((attrMatch = attrRegex.exec(attrString)) !== null) {
25
- const name = attrMatch[1]
26
- if (name) {
27
- const value = attrMatch[2] || attrMatch[3] || attrMatch[4] || 'true'
28
- attributes[name] = value
29
- }
30
- }
31
-
32
- if (content) {
33
- scripts.push(content.trim())
34
- }
35
- }
36
-
37
- if (scripts.length === 0) {
38
- return null
39
- }
40
-
41
- return {
42
- raw: scripts.join('\n\n'),
43
- attributes
44
- }
45
- }
46
-