@zenithbuild/core 0.4.7 → 0.5.0-beta.2.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 (110) 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 +41 -53
  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 -178
  38. package/compiler/discovery/layouts.ts +0 -70
  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 -82
  43. package/compiler/ir/types.ts +0 -162
  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 -599
  48. package/compiler/parse/parseZenFile.ts +0 -66
  49. package/compiler/parse/scriptAnalysis.ts +0 -91
  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 -363
  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 -312
  65. package/compiler/transform/componentScriptTransformer.ts +0 -147
  66. package/compiler/transform/expressionTransformer.ts +0 -385
  67. package/compiler/transform/fragmentLowering.ts +0 -634
  68. package/compiler/transform/generateBindings.ts +0 -47
  69. package/compiler/transform/generateHTML.ts +0 -28
  70. package/compiler/transform/layoutProcessor.ts +0 -132
  71. package/compiler/transform/slotResolver.ts +0 -292
  72. package/compiler/transform/transformNode.ts +0 -126
  73. package/compiler/transform/transformTemplate.ts +0 -38
  74. package/compiler/validate/invariants.ts +0 -292
  75. package/compiler/validate/validateExpressions.ts +0 -168
  76. package/core/config/index.ts +0 -16
  77. package/core/config/loader.ts +0 -69
  78. package/core/config/types.ts +0 -89
  79. package/core/index.ts +0 -135
  80. package/core/lifecycle/index.ts +0 -49
  81. package/core/lifecycle/zen-mount.ts +0 -182
  82. package/core/lifecycle/zen-unmount.ts +0 -88
  83. package/core/plugins/index.ts +0 -7
  84. package/core/plugins/registry.ts +0 -81
  85. package/core/reactivity/index.ts +0 -54
  86. package/core/reactivity/tracking.ts +0 -167
  87. package/core/reactivity/zen-batch.ts +0 -57
  88. package/core/reactivity/zen-effect.ts +0 -139
  89. package/core/reactivity/zen-memo.ts +0 -146
  90. package/core/reactivity/zen-ref.ts +0 -52
  91. package/core/reactivity/zen-signal.ts +0 -121
  92. package/core/reactivity/zen-state.ts +0 -180
  93. package/core/reactivity/zen-untrack.ts +0 -44
  94. package/dist/cli.js +0 -11659
  95. package/dist/zen-build.js +0 -15633
  96. package/dist/zen-dev.js +0 -15633
  97. package/dist/zen-preview.js +0 -15633
  98. package/dist/zenith.js +0 -15633
  99. package/router/index.ts +0 -76
  100. package/router/manifest.ts +0 -314
  101. package/router/navigation/ZenLink.zen +0 -231
  102. package/router/navigation/index.ts +0 -78
  103. package/router/navigation/zen-link.ts +0 -584
  104. package/router/runtime.ts +0 -458
  105. package/router/types.ts +0 -168
  106. package/runtime/build.ts +0 -17
  107. package/runtime/bundle-generator.ts +0 -943
  108. package/runtime/client-runtime.ts +0 -549
  109. package/runtime/serve.ts +0 -93
  110. 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,82 +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
- // componentScripts: [],
57
- styles
58
- }
59
-
60
- // Resolve components if components directory is provided
61
- if (options?.componentsDir) {
62
- const { discoverComponents } = require('./discovery/componentDiscovery')
63
- const { resolveComponentsInIR } = require('./transform/componentResolver')
64
-
65
- // Component resolution may throw InvariantError — let it propagate
66
- const components = discoverComponents(options.componentsDir)
67
- ir = resolveComponentsInIR(ir, components)
68
- }
69
-
70
- // Validate all compiler invariants after resolution
71
- // Throws InvariantError if any invariant is violated
72
- validateInvariants(ir, filePath)
73
-
74
- const compiled = transformTemplate(ir)
75
-
76
- try {
77
- const finalized = finalizeOutputOrThrow(ir, compiled)
78
- return { ir, compiled, finalized }
79
- } catch (error: any) {
80
- throw new Error(`Failed to finalize output for ${filePath}:\\n${error.message}`)
81
- }
82
- }
@@ -1,162 +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
- /**
10
- * Component Script IR - represents a component's script block
11
- * Used for collecting and bundling component scripts
12
- */
13
- export type ComponentScriptIR = {
14
- name: string // Component name (e.g., 'HeroSection')
15
- script: string // Raw script content
16
- props: string[] // Declared props
17
- scriptAttributes: Record<string, string> // Script attributes (setup, lang)
18
- }
19
-
20
- export type ZenIR = {
21
- filePath: string
22
- template: TemplateIR
23
- script: ScriptIR | null
24
- styles: StyleIR[]
25
- componentScripts?: ComponentScriptIR[] // Scripts from used components
26
- }
27
-
28
- export type TemplateIR = {
29
- raw: string
30
- nodes: TemplateNode[]
31
- expressions: ExpressionIR[]
32
- }
33
-
34
- export type TemplateNode =
35
- | ElementNode
36
- | TextNode
37
- | ExpressionNode
38
- | ComponentNode
39
- | ConditionalFragmentNode // JSX ternary: {cond ? <A /> : <B />}
40
- | OptionalFragmentNode // JSX logical AND: {cond && <A />}
41
- | LoopFragmentNode // JSX map: {items.map(i => <li>...</li>)}
42
-
43
- export type ElementNode = {
44
- type: 'element'
45
- tag: string
46
- attributes: AttributeIR[]
47
- children: TemplateNode[]
48
- location: SourceLocation
49
- loopContext?: LoopContext // Phase 7: Inherited loop context from parent map expressions
50
- }
51
-
52
- export type ComponentNode = {
53
- type: 'component'
54
- name: string
55
- attributes: AttributeIR[]
56
- children: TemplateNode[]
57
- location: SourceLocation
58
- loopContext?: LoopContext
59
- }
60
-
61
- export type TextNode = {
62
- type: 'text'
63
- value: string
64
- location: SourceLocation
65
- }
66
-
67
- export type ExpressionNode = {
68
- type: 'expression'
69
- expression: string
70
- location: SourceLocation
71
- loopContext?: LoopContext // Phase 7: Loop context for expressions inside map iterations
72
- }
73
-
74
- /**
75
- * Conditional Fragment Node
76
- *
77
- * Represents ternary expressions with JSX branches: {cond ? <A /> : <B />}
78
- *
79
- * BOTH branches are compiled at compile time.
80
- * Runtime toggles visibility — never creates DOM.
81
- */
82
- export type ConditionalFragmentNode = {
83
- type: 'conditional-fragment'
84
- condition: string // The condition expression code
85
- consequent: TemplateNode[] // Precompiled "true" branch
86
- alternate: TemplateNode[] // Precompiled "false" branch
87
- location: SourceLocation
88
- loopContext?: LoopContext
89
- }
90
-
91
- /**
92
- * Optional Fragment Node
93
- *
94
- * Represents logical AND expressions with JSX: {cond && <A />}
95
- *
96
- * Fragment is compiled at compile time.
97
- * Runtime toggles mount/unmount based on condition.
98
- */
99
- export type OptionalFragmentNode = {
100
- type: 'optional-fragment'
101
- condition: string // The condition expression code
102
- fragment: TemplateNode[] // Precompiled fragment
103
- location: SourceLocation
104
- loopContext?: LoopContext
105
- }
106
-
107
- /**
108
- * Loop Fragment Node
109
- *
110
- * Represents .map() expressions with JSX body: {items.map(i => <li>...</li>)}
111
- *
112
- * Desugars to @for loop semantics at compile time.
113
- * Body is compiled once, instantiated per item at runtime.
114
- * Node identity is compiler-owned via stable keys.
115
- */
116
- export type LoopFragmentNode = {
117
- type: 'loop-fragment'
118
- source: string // Array expression (e.g., 'items')
119
- itemVar: string // Loop variable (e.g., 'item')
120
- indexVar?: string // Optional index variable
121
- body: TemplateNode[] // Precompiled loop body template
122
- location: SourceLocation
123
- loopContext: LoopContext // Extended with this loop's variables
124
- }
125
-
126
- export type AttributeIR = {
127
- name: string
128
- value: string | ExpressionIR
129
- location: SourceLocation
130
- loopContext?: LoopContext // Phase 7: Loop context for expressions inside map iterations
131
- }
132
-
133
- /**
134
- * Loop context for expressions inside map iterations
135
- * Phase 7: Tracks loop variables (e.g., todo, index) for expressions inside .map() calls
136
- */
137
- export type LoopContext = {
138
- variables: string[] // e.g., ['todo', 'index'] for todoItems.map((todo, index) => ...)
139
- mapSource?: string // The array being mapped, e.g., 'todoItems'
140
- }
141
-
142
- export type ExpressionIR = {
143
- id: string
144
- code: string
145
- location: SourceLocation
146
- }
147
-
148
- export type ScriptIR = {
149
- raw: string
150
- attributes: Record<string, string>
151
- }
152
-
153
- export type StyleIR = {
154
- raw: string
155
- }
156
-
157
- export type SourceLocation = {
158
- line: number
159
- column: number
160
- }
161
-
162
-
@@ -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
-