@zenithbuild/core 1.2.2 → 1.2.3

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 (83) hide show
  1. package/README.md +20 -19
  2. package/cli/commands/add.ts +2 -2
  3. package/cli/commands/build.ts +2 -3
  4. package/cli/commands/dev.ts +93 -73
  5. package/cli/commands/index.ts +1 -1
  6. package/cli/commands/preview.ts +1 -1
  7. package/cli/commands/remove.ts +2 -2
  8. package/cli/index.ts +1 -1
  9. package/cli/main.ts +1 -1
  10. package/cli/utils/logger.ts +1 -1
  11. package/cli/utils/plugin-manager.ts +1 -1
  12. package/cli/utils/project.ts +4 -4
  13. package/core/components/ErrorPage.zen +218 -0
  14. package/core/components/index.ts +15 -0
  15. package/core/config.ts +1 -0
  16. package/core/index.ts +29 -0
  17. package/dist/compiler-native-frej59m4.node +0 -0
  18. package/dist/core/compiler-native-frej59m4.node +0 -0
  19. package/dist/core/index.js +6293 -0
  20. package/dist/runtime/lifecycle/index.js +1 -0
  21. package/dist/runtime/reactivity/index.js +1 -0
  22. package/dist/zen-build.js +1 -20118
  23. package/dist/zen-dev.js +1 -20118
  24. package/dist/zen-preview.js +1 -20118
  25. package/dist/zenith.js +1 -20118
  26. package/package.json +11 -20
  27. package/compiler/README.md +0 -380
  28. package/compiler/build-analyzer.ts +0 -122
  29. package/compiler/css/index.ts +0 -317
  30. package/compiler/discovery/componentDiscovery.ts +0 -242
  31. package/compiler/discovery/layouts.ts +0 -70
  32. package/compiler/errors/compilerError.ts +0 -56
  33. package/compiler/finalize/finalizeOutput.ts +0 -192
  34. package/compiler/finalize/generateFinalBundle.ts +0 -82
  35. package/compiler/index.ts +0 -83
  36. package/compiler/ir/types.ts +0 -174
  37. package/compiler/output/types.ts +0 -48
  38. package/compiler/parse/detectMapExpressions.ts +0 -102
  39. package/compiler/parse/importTypes.ts +0 -78
  40. package/compiler/parse/parseImports.ts +0 -309
  41. package/compiler/parse/parseScript.ts +0 -46
  42. package/compiler/parse/parseTemplate.ts +0 -628
  43. package/compiler/parse/parseZenFile.ts +0 -66
  44. package/compiler/parse/scriptAnalysis.ts +0 -91
  45. package/compiler/parse/trackLoopContext.ts +0 -82
  46. package/compiler/runtime/dataExposure.ts +0 -332
  47. package/compiler/runtime/generateDOM.ts +0 -255
  48. package/compiler/runtime/generateHydrationBundle.ts +0 -407
  49. package/compiler/runtime/hydration.ts +0 -309
  50. package/compiler/runtime/navigation.ts +0 -432
  51. package/compiler/runtime/thinRuntime.ts +0 -160
  52. package/compiler/runtime/transformIR.ts +0 -406
  53. package/compiler/runtime/wrapExpression.ts +0 -114
  54. package/compiler/runtime/wrapExpressionWithLoop.ts +0 -97
  55. package/compiler/spa-build.ts +0 -917
  56. package/compiler/ssg-build.ts +0 -486
  57. package/compiler/test/component-stacking.test.ts +0 -365
  58. package/compiler/test/map-lowering.test.ts +0 -130
  59. package/compiler/test/validate-test.ts +0 -104
  60. package/compiler/transform/classifyExpression.ts +0 -444
  61. package/compiler/transform/componentResolver.ts +0 -350
  62. package/compiler/transform/componentScriptTransformer.ts +0 -303
  63. package/compiler/transform/expressionTransformer.ts +0 -385
  64. package/compiler/transform/fragmentLowering.ts +0 -819
  65. package/compiler/transform/generateBindings.ts +0 -68
  66. package/compiler/transform/generateHTML.ts +0 -28
  67. package/compiler/transform/layoutProcessor.ts +0 -132
  68. package/compiler/transform/slotResolver.ts +0 -292
  69. package/compiler/transform/transformNode.ts +0 -314
  70. package/compiler/transform/transformTemplate.ts +0 -38
  71. package/compiler/validate/invariants.ts +0 -292
  72. package/compiler/validate/validateExpressions.ts +0 -168
  73. package/core/config/index.ts +0 -18
  74. package/core/config/loader.ts +0 -69
  75. package/core/config/types.ts +0 -119
  76. package/core/plugins/bridge.ts +0 -193
  77. package/core/plugins/index.ts +0 -7
  78. package/core/plugins/registry.ts +0 -126
  79. package/dist/cli.js +0 -11675
  80. package/runtime/build.ts +0 -17
  81. package/runtime/bundle-generator.ts +0 -1266
  82. package/runtime/client-runtime.ts +0 -891
  83. package/runtime/serve.ts +0 -93
@@ -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 async function finalizeOutput(
43
- ir: ZenIR,
44
- compiled: CompiledTemplate
45
- ): Promise<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 = await 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 async function finalizeOutputOrThrow(
180
- ir: ZenIR,
181
- compiled: CompiledTemplate
182
- ): Promise<FinalizedOutput> {
183
- const output = await 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,83 +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 async function compileZen(filePath: string): Promise<{
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 async function compileZenSource(
28
- source: string,
29
- filePath: string,
30
- options?: {
31
- componentsDir?: string
32
- }
33
- ): Promise<{
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 = /<style[^>]*>([\s\S]*?)<\/style>/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 = await 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
- }
83
-
@@ -1,174 +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
- * Structured ES module import metadata
11
- * Parsed from component scripts, used for deterministic bundling
12
- */
13
- export interface ScriptImport {
14
- source: string // Module specifier, e.g. 'gsap'
15
- specifiers: string // Import clause, e.g. '{ gsap }' or 'gsap' or ''
16
- typeOnly: boolean // TypeScript type-only import
17
- sideEffect: boolean // Side-effect import (no specifiers)
18
- }
19
-
20
- /**
21
- * Component Script IR - represents a component's script block
22
- * Used for collecting and bundling component scripts
23
- */
24
- export type ComponentScriptIR = {
25
- name: string // Component name (e.g., 'HeroSection')
26
- script: string // Raw script content
27
- props: string[] // Declared props
28
- scriptAttributes: Record<string, string> // Script attributes (setup, lang)
29
- imports: ScriptImport[] // Parsed npm imports for bundling
30
- }
31
-
32
- export type ZenIR = {
33
- filePath: string
34
- template: TemplateIR
35
- script: ScriptIR | null
36
- styles: StyleIR[]
37
- componentScripts?: ComponentScriptIR[] // Scripts from used components
38
- }
39
-
40
- export type TemplateIR = {
41
- raw: string
42
- nodes: TemplateNode[]
43
- expressions: ExpressionIR[]
44
- }
45
-
46
- export type TemplateNode =
47
- | ElementNode
48
- | TextNode
49
- | ExpressionNode
50
- | ComponentNode
51
- | ConditionalFragmentNode // JSX ternary: {cond ? <A /> : <B />}
52
- | OptionalFragmentNode // JSX logical AND: {cond && <A />}
53
- | LoopFragmentNode // JSX map: {items.map(i => <li>...</li>)}
54
-
55
- export type ElementNode = {
56
- type: 'element'
57
- tag: string
58
- attributes: AttributeIR[]
59
- children: TemplateNode[]
60
- location: SourceLocation
61
- loopContext?: LoopContext // Phase 7: Inherited loop context from parent map expressions
62
- }
63
-
64
- export type ComponentNode = {
65
- type: 'component'
66
- name: string
67
- attributes: AttributeIR[]
68
- children: TemplateNode[]
69
- location: SourceLocation
70
- loopContext?: LoopContext
71
- }
72
-
73
- export type TextNode = {
74
- type: 'text'
75
- value: string
76
- location: SourceLocation
77
- }
78
-
79
- export type ExpressionNode = {
80
- type: 'expression'
81
- expression: string
82
- location: SourceLocation
83
- loopContext?: LoopContext // Phase 7: Loop context for expressions inside map iterations
84
- }
85
-
86
- /**
87
- * Conditional Fragment Node
88
- *
89
- * Represents ternary expressions with JSX branches: {cond ? <A /> : <B />}
90
- *
91
- * BOTH branches are compiled at compile time.
92
- * Runtime toggles visibility — never creates DOM.
93
- */
94
- export type ConditionalFragmentNode = {
95
- type: 'conditional-fragment'
96
- condition: string // The condition expression code
97
- consequent: TemplateNode[] // Precompiled "true" branch
98
- alternate: TemplateNode[] // Precompiled "false" branch
99
- location: SourceLocation
100
- loopContext?: LoopContext
101
- }
102
-
103
- /**
104
- * Optional Fragment Node
105
- *
106
- * Represents logical AND expressions with JSX: {cond && <A />}
107
- *
108
- * Fragment is compiled at compile time.
109
- * Runtime toggles mount/unmount based on condition.
110
- */
111
- export type OptionalFragmentNode = {
112
- type: 'optional-fragment'
113
- condition: string // The condition expression code
114
- fragment: TemplateNode[] // Precompiled fragment
115
- location: SourceLocation
116
- loopContext?: LoopContext
117
- }
118
-
119
- /**
120
- * Loop Fragment Node
121
- *
122
- * Represents .map() expressions with JSX body: {items.map(i => <li>...</li>)}
123
- *
124
- * Desugars to @for loop semantics at compile time.
125
- * Body is compiled once, instantiated per item at runtime.
126
- * Node identity is compiler-owned via stable keys.
127
- */
128
- export type LoopFragmentNode = {
129
- type: 'loop-fragment'
130
- source: string // Array expression (e.g., 'items')
131
- itemVar: string // Loop variable (e.g., 'item')
132
- indexVar?: string // Optional index variable
133
- body: TemplateNode[] // Precompiled loop body template
134
- location: SourceLocation
135
- loopContext: LoopContext // Extended with this loop's variables
136
- }
137
-
138
- export type AttributeIR = {
139
- name: string
140
- value: string | ExpressionIR
141
- location: SourceLocation
142
- loopContext?: LoopContext // Phase 7: Loop context for expressions inside map iterations
143
- }
144
-
145
- /**
146
- * Loop context for expressions inside map iterations
147
- * Phase 7: Tracks loop variables (e.g., todo, index) for expressions inside .map() calls
148
- */
149
- export type LoopContext = {
150
- variables: string[] // e.g., ['todo', 'index'] for todoItems.map((todo, index) => ...)
151
- mapSource?: string // The array being mapped, e.g., 'todoItems'
152
- }
153
-
154
- export type ExpressionIR = {
155
- id: string
156
- code: string
157
- location: SourceLocation
158
- }
159
-
160
- export type ScriptIR = {
161
- raw: string
162
- attributes: Record<string, string>
163
- }
164
-
165
- export type StyleIR = {
166
- raw: string
167
- }
168
-
169
- export type SourceLocation = {
170
- line: number
171
- column: number
172
- }
173
-
174
-
@@ -1,48 +0,0 @@
1
- /**
2
- * Compiled Template Output Types
3
- *
4
- * Phase 2: Transform IR → Static HTML + Runtime Bindings
5
- * Phase 8: Extended with fragment binding types (loop, conditional, optional)
6
- */
7
-
8
- import type { TemplateNode } from '../ir/types'
9
-
10
- export type CompiledTemplate = {
11
- html: string
12
- bindings: Binding[]
13
- scripts: string | null
14
- styles: string[]
15
- }
16
-
17
- export type Binding = {
18
- id: string
19
- type: 'text' | 'attribute' | 'loop' | 'conditional' | 'optional'
20
- target: string // e.g., "data-zen-text" or "class" for attribute bindings
21
- expression: string // The original expression code
22
- location?: {
23
- line: number
24
- column: number
25
- }
26
- loopContext?: LoopContext // Phase 7: Loop context for expressions inside map iterations
27
- loopMeta?: LoopMeta // Phase 8: Metadata for loop bindings
28
- }
29
-
30
- /**
31
- * Loop binding metadata
32
- * Phase 8: Contains loop variable names and body template for runtime instantiation
33
- */
34
- export type LoopMeta = {
35
- itemVar: string
36
- indexVar?: string
37
- bodyTemplate: TemplateNode[]
38
- }
39
-
40
- /**
41
- * Loop context for expressions inside map iterations
42
- * Phase 7: Tracks loop variables for runtime setter generation
43
- */
44
- export type LoopContext = {
45
- variables: string[] // e.g., ['todo', 'index']
46
- mapSource?: string // The array being mapped, e.g., 'todoItems'
47
- }
48
-
@@ -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
-