@vesk/compiler 0.0.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.
- package/README.md +23 -0
- package/dist/acorn-ts-plugin/index.d.ts +482 -0
- package/dist/acorn-ts-plugin/index.d.ts.map +1 -0
- package/dist/acorn-ts-plugin/index.js +5028 -0
- package/dist/actions.d.ts +22 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +167 -0
- package/dist/api-routes.d.ts +30 -0
- package/dist/api-routes.d.ts.map +1 -0
- package/dist/api-routes.js +350 -0
- package/dist/client-codegen.d.ts +19 -0
- package/dist/client-codegen.d.ts.map +1 -0
- package/dist/client-codegen.js +1523 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +106 -0
- package/dist/errors.d.ts +33 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +210 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/ir-generator.d.ts +10 -0
- package/dist/ir-generator.d.ts.map +1 -0
- package/dist/ir-generator.js +1062 -0
- package/dist/ir.d.ts +160 -0
- package/dist/ir.d.ts.map +1 -0
- package/dist/ir.js +219 -0
- package/dist/middleware.d.ts +15 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +183 -0
- package/dist/package.json +15 -0
- package/dist/parser.d.ts +38 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +242 -0
- package/dist/router.d.ts +31 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +451 -0
- package/dist/scan.d.ts +106 -0
- package/dist/scan.d.ts.map +1 -0
- package/dist/scan.js +438 -0
- package/dist/server-codegen.d.ts +5 -0
- package/dist/server-codegen.d.ts.map +1 -0
- package/dist/server-codegen.js +4 -0
- package/dist/server-cookies.d.ts +2 -0
- package/dist/server-cookies.d.ts.map +1 -0
- package/dist/server-cookies.js +15 -0
- package/dist/server-head.d.ts +10 -0
- package/dist/server-head.d.ts.map +1 -0
- package/dist/server-head.js +289 -0
- package/dist/server-jsgen.d.ts +6 -0
- package/dist/server-jsgen.d.ts.map +1 -0
- package/dist/server-jsgen.js +446 -0
- package/dist/server-render.d.ts +16 -0
- package/dist/server-render.d.ts.map +1 -0
- package/dist/server-render.js +397 -0
- package/dist/server-utils.d.ts +93 -0
- package/dist/server-utils.d.ts.map +1 -0
- package/dist/server-utils.js +747 -0
- package/dist/ssr-store.d.ts +4 -0
- package/dist/ssr-store.d.ts.map +1 -0
- package/dist/ssr-store.js +29 -0
- package/dist/strip-ts.d.ts +25 -0
- package/dist/strip-ts.d.ts.map +1 -0
- package/dist/strip-ts.js +171 -0
- package/dist/tokens.d.ts +50 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +334 -0
- package/dist/typecheck.d.ts +19 -0
- package/dist/typecheck.d.ts.map +1 -0
- package/dist/typecheck.js +365 -0
- package/dist/types.d.ts +147 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/vesk-plugin.d.ts +53 -0
- package/dist/vesk-plugin.d.ts.map +1 -0
- package/dist/vesk-plugin.js +413 -0
- package/dist/vsk-imports.d.ts +30 -0
- package/dist/vsk-imports.d.ts.map +1 -0
- package/dist/vsk-imports.js +141 -0
- package/dist/vsk-tsx.d.ts +3 -0
- package/dist/vsk-tsx.d.ts.map +1 -0
- package/dist/vsk-tsx.js +604 -0
- package/package.json +52 -0
|
@@ -0,0 +1,1062 @@
|
|
|
1
|
+
import { IRRoot, ComponentIR, StaticNode, TextNode, DynamicBinding, OpaqueDynamicRegion, MapRegion, WhileLoop, SwitchBlock, TryCatch, RuntimeStatement, ForLoop, TrackDecl, ComponentRef, ComponentCall, ServerBlock, ClientBlock, HeadBlock, Expression, SlotNode, } from '@vesk/compiler/src/ir';
|
|
2
|
+
import { VeskError } from '@vesk/compiler/src/errors';
|
|
3
|
+
import { createBaseParser } from '@vesk/compiler/src/parser';
|
|
4
|
+
import { skipWhitespace, findBalancedEnd, splitTopLevel, startsWithIdentifier, stripDeclKeyword, isWhitespaceChar, collapseNewlineWhitespace } from '@vesk/compiler/src/scan';
|
|
5
|
+
import { stripCodeTypes } from '@vesk/compiler/src/strip-ts';
|
|
6
|
+
import { stripTypeImport } from '@vesk/compiler/src/vsk-imports';
|
|
7
|
+
import { collectCalledIdentifiers, extractImportNames, importModuleTarget } from '@vesk/compiler/src/tokens';
|
|
8
|
+
let __vskAnnotations = [];
|
|
9
|
+
function parseExprNode(text) {
|
|
10
|
+
try {
|
|
11
|
+
const ParserClass = createBaseParser();
|
|
12
|
+
const ast = ParserClass.parse(`(${text})`, { ecmaVersion: 'latest', sourceType: 'module' });
|
|
13
|
+
return ast.body[0]?.expression ?? null;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function getForClauseAnnotation(forStart) {
|
|
20
|
+
let keyRange;
|
|
21
|
+
let indexName;
|
|
22
|
+
let clauseStart = -1;
|
|
23
|
+
let clauseEnd = -1;
|
|
24
|
+
for (const ann of __vskAnnotations) {
|
|
25
|
+
if (ann.kind !== 'for-clause' || ann.forStart !== forStart)
|
|
26
|
+
continue;
|
|
27
|
+
if (ann.keyRange)
|
|
28
|
+
keyRange = ann.keyRange;
|
|
29
|
+
if (ann.indexName)
|
|
30
|
+
indexName = ann.indexName;
|
|
31
|
+
clauseStart = ann.clauseStart;
|
|
32
|
+
clauseEnd = ann.clauseEnd;
|
|
33
|
+
}
|
|
34
|
+
if (clauseStart === -1)
|
|
35
|
+
return null;
|
|
36
|
+
return { kind: 'for-clause', forStart, clauseStart, clauseEnd, ...(keyRange ? { keyRange } : {}), ...(indexName !== undefined ? { indexName } : {}) };
|
|
37
|
+
}
|
|
38
|
+
function getSource(source, node) {
|
|
39
|
+
return source.slice(node.start, node.end);
|
|
40
|
+
}
|
|
41
|
+
function collectComponentCalls(nodes, out) {
|
|
42
|
+
for (const n of nodes) {
|
|
43
|
+
if (n instanceof ComponentCall) {
|
|
44
|
+
out.set(n.componentName, n.start);
|
|
45
|
+
collectComponentCalls(n.children, out);
|
|
46
|
+
}
|
|
47
|
+
else if (n instanceof StaticNode || n instanceof ServerBlock || n instanceof ClientBlock || n instanceof HeadBlock) {
|
|
48
|
+
collectComponentCalls(n.children, out);
|
|
49
|
+
}
|
|
50
|
+
else if (n instanceof MapRegion) {
|
|
51
|
+
collectComponentCalls(n.bodyTemplate, out);
|
|
52
|
+
collectComponentCalls(n.alternateNodes, out);
|
|
53
|
+
}
|
|
54
|
+
else if (n instanceof OpaqueDynamicRegion) {
|
|
55
|
+
collectComponentCalls(n.consequentNodes, out);
|
|
56
|
+
collectComponentCalls(n.alternateNodes, out);
|
|
57
|
+
}
|
|
58
|
+
else if (n instanceof WhileLoop || n instanceof ForLoop) {
|
|
59
|
+
collectComponentCalls(n.bodyTemplate, out);
|
|
60
|
+
}
|
|
61
|
+
else if (n instanceof TryCatch) {
|
|
62
|
+
collectComponentCalls(n.bodyTemplate, out);
|
|
63
|
+
collectComponentCalls(n.catchBody, out);
|
|
64
|
+
}
|
|
65
|
+
else if (n instanceof SwitchBlock) {
|
|
66
|
+
for (const c of n.cases)
|
|
67
|
+
collectComponentCalls(c.body, out);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function offsetToLineCol(source, offset) {
|
|
72
|
+
let line = 1;
|
|
73
|
+
let column = 1;
|
|
74
|
+
for (let i = 0; i < offset && i < source.length; i++) {
|
|
75
|
+
if (source.charCodeAt(i) === 10) {
|
|
76
|
+
line++;
|
|
77
|
+
column = 1;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
column++;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return { line, column };
|
|
84
|
+
}
|
|
85
|
+
function componentUsesFetch(nodes) {
|
|
86
|
+
for (const node of nodes) {
|
|
87
|
+
if (node instanceof ServerBlock || node instanceof ClientBlock) {
|
|
88
|
+
if (componentUsesFetch(node.children))
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
else if (node instanceof RuntimeStatement) {
|
|
92
|
+
if (node.raw.includes('useFetch('))
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
else if (node instanceof DynamicBinding) {
|
|
96
|
+
if (node.expression.raw.includes('useFetch('))
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
else if (node instanceof MapRegion) {
|
|
100
|
+
if (componentUsesFetch(node.bodyTemplate))
|
|
101
|
+
return true;
|
|
102
|
+
if (componentUsesFetch(node.alternateNodes))
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
else if (node instanceof OpaqueDynamicRegion) {
|
|
106
|
+
if (componentUsesFetch(node.consequentNodes) || componentUsesFetch(node.alternateNodes))
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
else if (node instanceof WhileLoop) {
|
|
110
|
+
if (componentUsesFetch(node.bodyTemplate))
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
else if (node instanceof ForLoop) {
|
|
114
|
+
if (componentUsesFetch(node.bodyTemplate))
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
else if (node instanceof TryCatch) {
|
|
118
|
+
if (componentUsesFetch(node.bodyTemplate) || componentUsesFetch(node.catchBody))
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
else if (node instanceof SwitchBlock) {
|
|
122
|
+
for (const c of node.cases) {
|
|
123
|
+
if (componentUsesFetch(c.body))
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
function extractKeyExpr(nodes) {
|
|
131
|
+
for (const n of nodes) {
|
|
132
|
+
if (n instanceof StaticNode && n.keyExpr)
|
|
133
|
+
return n.keyExpr;
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
function isTrackDeclaration(decl) {
|
|
138
|
+
return (decl.type === 'VariableDeclaration' &&
|
|
139
|
+
decl.declarations.length === 1 &&
|
|
140
|
+
decl.declarations[0].id.type === 'ArrayPattern' &&
|
|
141
|
+
decl.declarations[0].id.lazy === true);
|
|
142
|
+
}
|
|
143
|
+
function getParamNames(params, source) {
|
|
144
|
+
return params.map((p) => {
|
|
145
|
+
if (p.type === 'Identifier')
|
|
146
|
+
return [p.name];
|
|
147
|
+
if (p.type === 'ObjectPattern')
|
|
148
|
+
return p.properties.map((prop) => {
|
|
149
|
+
const name = prop.key.name || prop.key.value;
|
|
150
|
+
if (prop.value.type === 'AssignmentPattern') {
|
|
151
|
+
const defaultSrc = source.slice(prop.value.right.start, prop.value.right.end);
|
|
152
|
+
return `${name} = ${defaultSrc}`;
|
|
153
|
+
}
|
|
154
|
+
return name;
|
|
155
|
+
});
|
|
156
|
+
if (p.type === 'ArrayPattern')
|
|
157
|
+
return p.elements.map((el) => el?.name ?? '_');
|
|
158
|
+
return ['_'];
|
|
159
|
+
}).flat();
|
|
160
|
+
}
|
|
161
|
+
export function getPropsType(params, source) {
|
|
162
|
+
if (!params || params.length === 0)
|
|
163
|
+
return null;
|
|
164
|
+
if (params.length === 1) {
|
|
165
|
+
const p = params[0];
|
|
166
|
+
const inner = p.typeAnnotation?.typeAnnotation;
|
|
167
|
+
if (inner)
|
|
168
|
+
return source.slice(inner.start, inner.end).trim();
|
|
169
|
+
const left = p.type === 'AssignmentPattern' ? p.left : null;
|
|
170
|
+
const innerLeft = left?.typeAnnotation?.typeAnnotation;
|
|
171
|
+
if (innerLeft)
|
|
172
|
+
return source.slice(innerLeft.start, innerLeft.end).trim();
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
const members = [];
|
|
176
|
+
for (const p of params) {
|
|
177
|
+
let name = null;
|
|
178
|
+
let optional = false;
|
|
179
|
+
let type = 'any';
|
|
180
|
+
if (p.type === 'Identifier') {
|
|
181
|
+
name = p.name ?? null;
|
|
182
|
+
const inner = p.typeAnnotation?.typeAnnotation;
|
|
183
|
+
if (inner)
|
|
184
|
+
type = source.slice(inner.start, inner.end).trim();
|
|
185
|
+
}
|
|
186
|
+
else if (p.type === 'AssignmentPattern') {
|
|
187
|
+
optional = true;
|
|
188
|
+
name = p.left?.name ?? null;
|
|
189
|
+
const inner = p.left?.typeAnnotation?.typeAnnotation;
|
|
190
|
+
if (inner)
|
|
191
|
+
type = source.slice(inner.start, inner.end).trim();
|
|
192
|
+
}
|
|
193
|
+
else if (p.type === 'ObjectPattern') {
|
|
194
|
+
const inner = p.typeAnnotation?.typeAnnotation;
|
|
195
|
+
if (inner)
|
|
196
|
+
return source.slice(inner.start, inner.end).trim();
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (name)
|
|
200
|
+
members.push(`${JSON.stringify(name)}${optional ? '?' : ''}: ${type}`);
|
|
201
|
+
}
|
|
202
|
+
if (members.length === 0)
|
|
203
|
+
return null;
|
|
204
|
+
return `{ ${members.join('; ')} }`;
|
|
205
|
+
}
|
|
206
|
+
function getJSXTagName(nameNode) {
|
|
207
|
+
if (nameNode.type === 'JSXIdentifier')
|
|
208
|
+
return nameNode.name;
|
|
209
|
+
if (nameNode.type === 'JSXMemberExpression') {
|
|
210
|
+
return getJSXTagName(nameNode.object) + '.' + getJSXTagName(nameNode.property);
|
|
211
|
+
}
|
|
212
|
+
return 'unknown';
|
|
213
|
+
}
|
|
214
|
+
function isHTMLTag(name) {
|
|
215
|
+
return name.length > 0 && name[0] === name[0].toLowerCase();
|
|
216
|
+
}
|
|
217
|
+
function isMapCall(expr) {
|
|
218
|
+
return (expr.type === 'CallExpression' &&
|
|
219
|
+
expr.callee.type === 'MemberExpression' &&
|
|
220
|
+
expr.callee.property.name === 'map' &&
|
|
221
|
+
expr.arguments.length === 1 &&
|
|
222
|
+
expr.arguments[0].type === 'ArrowFunctionExpression');
|
|
223
|
+
}
|
|
224
|
+
function toExpression(source, expr) {
|
|
225
|
+
return new Expression(getSource(source, expr), [], expr, source);
|
|
226
|
+
}
|
|
227
|
+
function processAttribute(source, attr) {
|
|
228
|
+
const name = attr.name.type === 'JSXIdentifier' ? attr.name.name : getSource(source, attr.name);
|
|
229
|
+
if (attr.value === null)
|
|
230
|
+
return { name, value: '' };
|
|
231
|
+
if (attr.value.type === 'Literal')
|
|
232
|
+
return { name, value: String(attr.value.value) };
|
|
233
|
+
if (attr.value.type === 'JSXExpressionContainer') {
|
|
234
|
+
const expr = attr.value.expression;
|
|
235
|
+
if (expr.type === 'Literal')
|
|
236
|
+
return { name, value: String(expr.value) };
|
|
237
|
+
return { name, value: toExpression(source, expr) };
|
|
238
|
+
}
|
|
239
|
+
return { name, value: '' };
|
|
240
|
+
}
|
|
241
|
+
function extractForHeader(text) {
|
|
242
|
+
if (!startsWithIdentifier(text, 'for'))
|
|
243
|
+
return null;
|
|
244
|
+
let i = skipWhitespace(text, 3);
|
|
245
|
+
if (text[i] !== '(')
|
|
246
|
+
return null;
|
|
247
|
+
const end = findBalancedEnd(text, i);
|
|
248
|
+
return text.slice(i + 1, end);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Returns the offset of the `for` keyword in a JSXText value (ignoring
|
|
252
|
+
* leading whitespace), or -1 when the value does not begin with a `for`.
|
|
253
|
+
*/
|
|
254
|
+
function hasForPrefix(text) {
|
|
255
|
+
if (!startsWithIdentifier(text, 'for'))
|
|
256
|
+
return -1;
|
|
257
|
+
let i = 0;
|
|
258
|
+
while (i < text.length && isWhitespaceChar(text[i]))
|
|
259
|
+
i++;
|
|
260
|
+
return i;
|
|
261
|
+
}
|
|
262
|
+
function processJSXChildren(source, children) {
|
|
263
|
+
const result = [];
|
|
264
|
+
let i = 0;
|
|
265
|
+
while (i < children.length) {
|
|
266
|
+
const child = children[i];
|
|
267
|
+
if (child.type === 'JSXText') {
|
|
268
|
+
const text = collapseNewlineWhitespace(child.value);
|
|
269
|
+
const trimmed = text.trim();
|
|
270
|
+
if (!trimmed || trimmed.startsWith('//')) {
|
|
271
|
+
i++;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const forDecl = extractForHeader(trimmed);
|
|
275
|
+
const nextChild = children[i + 1];
|
|
276
|
+
if (forDecl !== null && nextChild !== undefined && (nextChild.type === 'JSXExpressionContainer' ||
|
|
277
|
+
nextChild.type === 'JSXElement' ||
|
|
278
|
+
nextChild.type === 'JSXFragment')) {
|
|
279
|
+
const decl = forDecl;
|
|
280
|
+
let bodyNodes;
|
|
281
|
+
if (nextChild.type === 'JSXExpressionContainer') {
|
|
282
|
+
const exprNode = nextChild.expression;
|
|
283
|
+
if (exprNode.type === 'JSXEmptyExpression') {
|
|
284
|
+
result.push(new TextNode(text));
|
|
285
|
+
i++;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (exprNode.type === 'JSXFragment') {
|
|
289
|
+
bodyNodes = [];
|
|
290
|
+
for (const c of exprNode.children)
|
|
291
|
+
bodyNodes.push(...processJSXChildren(source, [c]));
|
|
292
|
+
}
|
|
293
|
+
else if (exprNode.type === 'JSXElement') {
|
|
294
|
+
bodyNodes = processJSXElement(source, exprNode);
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
bodyNodes = exprToIR(source, exprNode);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
else if (nextChild.type === 'JSXFragment') {
|
|
301
|
+
bodyNodes = [];
|
|
302
|
+
for (const c of nextChild.children)
|
|
303
|
+
bodyNodes.push(...processJSXChildren(source, [c]));
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
bodyNodes = processJSXElement(source, nextChild);
|
|
307
|
+
}
|
|
308
|
+
const ofParts = splitTopLevel(decl, 'of');
|
|
309
|
+
if (ofParts.length === 2) {
|
|
310
|
+
const itemVar = stripDeclKeyword(ofParts[0]).trim();
|
|
311
|
+
const exprText = ofParts[1].trim();
|
|
312
|
+
const arrExpr = new Expression(exprText, [], parseExprNode(exprText), null);
|
|
313
|
+
const rawFor = hasForPrefix(child.value);
|
|
314
|
+
const ann = rawFor ? getForClauseAnnotation(child.start + rawFor) : null;
|
|
315
|
+
const keyExpr = ann?.keyRange ? new Expression(source.slice(ann.keyRange[0], ann.keyRange[1])) : null;
|
|
316
|
+
const indexVar = ann?.indexName ?? null;
|
|
317
|
+
let alternate = [];
|
|
318
|
+
let consumed = 2;
|
|
319
|
+
const emptyText = children[i + 2];
|
|
320
|
+
const emptyContainer = children[i + 3];
|
|
321
|
+
if (emptyText && emptyText.type === 'JSXText' && ['#empty', 'empty'].includes(emptyText.value.trim()) &&
|
|
322
|
+
emptyContainer && emptyContainer.type === 'JSXExpressionContainer' &&
|
|
323
|
+
emptyContainer.expression.type !== 'JSXEmptyExpression') {
|
|
324
|
+
alternate = exprToIR(source, emptyContainer.expression);
|
|
325
|
+
consumed = 4;
|
|
326
|
+
}
|
|
327
|
+
result.push(new MapRegion(arrExpr, itemVar, bodyNodes, keyExpr, indexVar, alternate));
|
|
328
|
+
i += consumed;
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
const inParts = splitTopLevel(decl, 'in');
|
|
332
|
+
if (inParts.length === 2) {
|
|
333
|
+
const itemVar = inParts[0].trim();
|
|
334
|
+
const arrExpr = new Expression(inParts[1].trim());
|
|
335
|
+
result.push(new ForLoop(itemVar, arrExpr, '', bodyNodes, 'for-in'));
|
|
336
|
+
i += 2;
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
result.push(new TextNode(text));
|
|
341
|
+
i++;
|
|
342
|
+
}
|
|
343
|
+
else if (child.type === 'JSXExpressionContainer') {
|
|
344
|
+
const expr = child.expression;
|
|
345
|
+
if (expr.type === 'JSXEmptyExpression') {
|
|
346
|
+
i++;
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
if (isMapCall(expr)) {
|
|
350
|
+
const arrowFn = expr.arguments[0];
|
|
351
|
+
const itemVar = arrowFn.params[0]?.name ?? 'item';
|
|
352
|
+
const indexVar = arrowFn.params[1]?.name ?? null;
|
|
353
|
+
const bodyNodes = processJSXCallbackBody(source, arrowFn.body);
|
|
354
|
+
const arrayExpr = toExpression(source, expr.callee.object);
|
|
355
|
+
const keyExpr = extractKeyExpr(bodyNodes);
|
|
356
|
+
result.push(new MapRegion(arrayExpr, itemVar, bodyNodes, keyExpr, indexVar));
|
|
357
|
+
i++;
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if (expr.type === 'LogicalExpression' && expr.operator === '&&') {
|
|
361
|
+
const condExpr = toExpression(source, expr.left);
|
|
362
|
+
const consequent = exprToIR(source, expr.right);
|
|
363
|
+
result.push(new OpaqueDynamicRegion(condExpr, consequent));
|
|
364
|
+
i++;
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
if (expr.type === 'ConditionalExpression') {
|
|
368
|
+
const condExpr = toExpression(source, expr.test);
|
|
369
|
+
const consequent = exprToIR(source, expr.consequent);
|
|
370
|
+
const alternate = exprToIR(source, expr.alternate);
|
|
371
|
+
result.push(new OpaqueDynamicRegion(condExpr, consequent, alternate));
|
|
372
|
+
i++;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if ((expr.type === 'MemberExpression' && !expr.computed &&
|
|
376
|
+
expr.object.type === 'Identifier' && expr.object.name === 'props' &&
|
|
377
|
+
expr.property.type === 'Identifier' && expr.property.name === 'children')
|
|
378
|
+
|| (expr.type === 'Identifier' && expr.name === 'children')) {
|
|
379
|
+
result.push(new SlotNode());
|
|
380
|
+
i++;
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
result.push(new DynamicBinding(toExpression(source, expr)));
|
|
384
|
+
i++;
|
|
385
|
+
}
|
|
386
|
+
else if (child.type === 'JSXElement') {
|
|
387
|
+
result.push(...processJSXElement(source, child));
|
|
388
|
+
i++;
|
|
389
|
+
}
|
|
390
|
+
else if (child.type === 'JSXFragment') {
|
|
391
|
+
for (const c of child.children)
|
|
392
|
+
result.push(...processJSXChildren(source, [c]));
|
|
393
|
+
i++;
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
i++;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return result;
|
|
400
|
+
}
|
|
401
|
+
function exprToIR(source, expr) {
|
|
402
|
+
if (expr.type === 'JSXElement')
|
|
403
|
+
return processJSXElement(source, expr);
|
|
404
|
+
if (expr.type === 'JSXFragment') {
|
|
405
|
+
const nodes = [];
|
|
406
|
+
for (const c of expr.children)
|
|
407
|
+
nodes.push(...processJSXChildren(source, [c]));
|
|
408
|
+
return nodes;
|
|
409
|
+
}
|
|
410
|
+
if (isMapCall(expr)) {
|
|
411
|
+
const arrowFn = expr.arguments[0];
|
|
412
|
+
const itemVar = arrowFn.params[0]?.name ?? 'item';
|
|
413
|
+
const indexVar = arrowFn.params[1]?.name ?? null;
|
|
414
|
+
const bodyNodes = processJSXCallbackBody(source, arrowFn.body);
|
|
415
|
+
const arrayExpr = toExpression(source, expr.callee.object);
|
|
416
|
+
const keyExpr = extractKeyExpr(bodyNodes);
|
|
417
|
+
return [new MapRegion(arrayExpr, itemVar, bodyNodes, keyExpr, indexVar)];
|
|
418
|
+
}
|
|
419
|
+
if (expr.type === 'ParenthesizedExpression')
|
|
420
|
+
return exprToIR(source, expr.expression);
|
|
421
|
+
// Nested conditionals/`&&` with JSX branches become nested dynamic regions
|
|
422
|
+
// so `a ? <X/> : b ? <Y/> : <Z/>` compiles recursively instead of degrading
|
|
423
|
+
// to a raw text binding.
|
|
424
|
+
if (expr.type === 'ConditionalExpression') {
|
|
425
|
+
const condExpr = toExpression(source, expr.test);
|
|
426
|
+
const consequent = exprToIR(source, expr.consequent);
|
|
427
|
+
const alternate = exprToIR(source, expr.alternate);
|
|
428
|
+
return [new OpaqueDynamicRegion(condExpr, consequent, alternate)];
|
|
429
|
+
}
|
|
430
|
+
if (expr.type === 'LogicalExpression' && expr.operator === '&&') {
|
|
431
|
+
const condExpr = toExpression(source, expr.left);
|
|
432
|
+
const consequent = exprToIR(source, expr.right);
|
|
433
|
+
return [new OpaqueDynamicRegion(condExpr, consequent)];
|
|
434
|
+
}
|
|
435
|
+
return [new DynamicBinding(toExpression(source, expr))];
|
|
436
|
+
}
|
|
437
|
+
function processJSXCallbackBody(source, body) {
|
|
438
|
+
if (body.type === 'JSXElement')
|
|
439
|
+
return processJSXElement(source, body);
|
|
440
|
+
if (body.type === 'JSXFragment') {
|
|
441
|
+
const nodes = [];
|
|
442
|
+
for (const c of body.children)
|
|
443
|
+
nodes.push(...processJSXChildren(source, [c]));
|
|
444
|
+
return nodes;
|
|
445
|
+
}
|
|
446
|
+
if (body.type === 'ParenthesizedExpression')
|
|
447
|
+
return exprToIR(source, body.expression);
|
|
448
|
+
return exprToIR(source, body);
|
|
449
|
+
}
|
|
450
|
+
function processJSXElement(source, element) {
|
|
451
|
+
const tagName = getJSXTagName(element.openingElement.name);
|
|
452
|
+
const selfClosing = element.openingElement.selfClosing;
|
|
453
|
+
if (tagName === 'Head') {
|
|
454
|
+
const children = selfClosing ? [] : processJSXChildren(source, element.children || []);
|
|
455
|
+
return [new HeadBlock(children)];
|
|
456
|
+
}
|
|
457
|
+
if (!isHTMLTag(tagName) && selfClosing) {
|
|
458
|
+
const { props, spreadProps } = extractProps(source, element);
|
|
459
|
+
return [new ComponentCall(tagName, props, [], spreadProps, element.start)];
|
|
460
|
+
}
|
|
461
|
+
if (!isHTMLTag(tagName)) {
|
|
462
|
+
const { props, spreadProps } = extractProps(source, element);
|
|
463
|
+
const children = processJSXChildren(source, element.children || []);
|
|
464
|
+
return [new ComponentCall(tagName, props, children, spreadProps, element.start)];
|
|
465
|
+
}
|
|
466
|
+
const attributes = element.openingElement.attributes
|
|
467
|
+
.filter((attr) => attr.type !== 'JSXSpreadAttribute')
|
|
468
|
+
.map((attr) => processAttribute(source, attr));
|
|
469
|
+
const staticAttrs = [];
|
|
470
|
+
const attrBindings = [];
|
|
471
|
+
let keyExpr = null;
|
|
472
|
+
for (const attr of attributes) {
|
|
473
|
+
if (attr.name === 'key') {
|
|
474
|
+
keyExpr = typeof attr.value === 'string' ? new Expression(JSON.stringify(attr.value)) : attr.value;
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
if (attr.name === 'ref') {
|
|
478
|
+
attrBindings.push(new DynamicBinding(attr.value, 'attribute', attr.name));
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
if (typeof attr.value === 'string') {
|
|
482
|
+
staticAttrs.push({ name: attr.name, value: attr.value });
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
staticAttrs.push({ name: attr.name, value: '' });
|
|
486
|
+
attrBindings.push(new DynamicBinding(attr.value, 'attribute', attr.name));
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
const children = selfClosing ? [] : processJSXChildren(source, element.children || []);
|
|
490
|
+
const node = new StaticNode(tagName, staticAttrs, [...attrBindings, ...children], keyExpr);
|
|
491
|
+
node.selfClosing = selfClosing;
|
|
492
|
+
return [node];
|
|
493
|
+
}
|
|
494
|
+
function extractProps(source, element) {
|
|
495
|
+
const props = [];
|
|
496
|
+
const spreadProps = [];
|
|
497
|
+
for (const attr of element.openingElement.attributes) {
|
|
498
|
+
if (attr.type === 'JSXSpreadAttribute') {
|
|
499
|
+
spreadProps.push(toExpression(source, attr.argument));
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
props.push({
|
|
503
|
+
name: attr.name.type === 'JSXIdentifier' ? attr.name.name : getSource(source, attr.name),
|
|
504
|
+
value: attr.value === null
|
|
505
|
+
? new Expression('true')
|
|
506
|
+
: attr.value.type === 'JSXExpressionContainer'
|
|
507
|
+
? toExpression(source, attr.value.expression)
|
|
508
|
+
: new Expression(JSON.stringify(attr.value.value)),
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return { props, spreadProps };
|
|
513
|
+
}
|
|
514
|
+
function buildGuardChain(source, guardClauses, mainReturn) {
|
|
515
|
+
const mainBody = [];
|
|
516
|
+
if (mainReturn && mainReturn.argument) {
|
|
517
|
+
if (mainReturn.argument.type === 'JSXElement') {
|
|
518
|
+
mainBody.push(...processJSXElement(source, mainReturn.argument));
|
|
519
|
+
}
|
|
520
|
+
else if (mainReturn.argument.type === 'JSXFragment') {
|
|
521
|
+
for (const c of mainReturn.argument.children) {
|
|
522
|
+
mainBody.push(...processJSXChildren(source, [c]));
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
mainBody.push(new DynamicBinding(toExpression(source, mainReturn.argument)));
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
let currentAlternate = mainBody;
|
|
530
|
+
for (let i = guardClauses.length - 1; i >= 0; i--) {
|
|
531
|
+
const guard = guardClauses[i];
|
|
532
|
+
const condExpr = toExpression(source, guard.test);
|
|
533
|
+
const consequent = [];
|
|
534
|
+
if (guard.consequent.type === 'ReturnStatement' && guard.consequent.argument) {
|
|
535
|
+
if (guard.consequent.argument.type === 'JSXElement') {
|
|
536
|
+
consequent.push(...processJSXElement(source, guard.consequent.argument));
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
consequent.push(new DynamicBinding(toExpression(source, guard.consequent.argument)));
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
currentAlternate = [new OpaqueDynamicRegion(condExpr, consequent, currentAlternate)];
|
|
543
|
+
}
|
|
544
|
+
return currentAlternate;
|
|
545
|
+
}
|
|
546
|
+
function getComponentRefName(decl) {
|
|
547
|
+
if (!isTrackDeclaration(decl))
|
|
548
|
+
return null;
|
|
549
|
+
const pattern = decl.declarations[0].id;
|
|
550
|
+
if (pattern.type === 'ArrayPattern' && pattern.elements.length === 1) {
|
|
551
|
+
const name = pattern.elements[0]?.name;
|
|
552
|
+
if (name && name[0] === name[0].toUpperCase())
|
|
553
|
+
return name;
|
|
554
|
+
}
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
557
|
+
function hasJSXInSubtree(node) {
|
|
558
|
+
if (!node)
|
|
559
|
+
return false;
|
|
560
|
+
if (node.type === 'JSXElement' || node.type === 'JSXExpressionContainer' || node.type === 'JSXFragment')
|
|
561
|
+
return true;
|
|
562
|
+
if (node.type === 'BlockStatement')
|
|
563
|
+
return node.body.some(hasJSXInSubtree);
|
|
564
|
+
if (node.type === 'IfStatement')
|
|
565
|
+
return hasJSXInSubtree(node.consequent) || hasJSXInSubtree(node.alternate);
|
|
566
|
+
if (node.type === 'ForStatement' || node.type === 'ForInStatement' || node.type === 'ForOfStatement')
|
|
567
|
+
return hasJSXInSubtree(node.body);
|
|
568
|
+
if (node.type === 'WhileStatement' || node.type === 'DoWhileStatement')
|
|
569
|
+
return hasJSXInSubtree(node.body);
|
|
570
|
+
if (node.type === 'SwitchStatement')
|
|
571
|
+
return node.cases.some((c) => c.consequent?.some(hasJSXInSubtree));
|
|
572
|
+
if (node.type === 'TryStatement')
|
|
573
|
+
return hasJSXInSubtree(node.block) || hasJSXInSubtree(node.handler) || hasJSXInSubtree(node.finalizer);
|
|
574
|
+
if (node.type === 'CatchClause')
|
|
575
|
+
return hasJSXInSubtree(node.body);
|
|
576
|
+
if (node.type === 'LabeledStatement')
|
|
577
|
+
return hasJSXInSubtree(node.body);
|
|
578
|
+
if (node.type === 'ReturnStatement')
|
|
579
|
+
return hasJSXInSubtree(node.argument);
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
function isGuardClause(node) {
|
|
583
|
+
return node.type === 'IfStatement' && node.consequent.type === 'ReturnStatement' && hasJSXInSubtree(node.consequent);
|
|
584
|
+
}
|
|
585
|
+
function isStatementMode(bodyStmts) {
|
|
586
|
+
if (bodyStmts.some((s) => s.type === 'JSXElement' || s.type === 'JSXExpressionContainer' || s.type === 'JSXFragment'))
|
|
587
|
+
return true;
|
|
588
|
+
for (const stmt of bodyStmts) {
|
|
589
|
+
if (stmt.type === 'IfStatement' && !isGuardClause(stmt) && hasJSXInSubtree(stmt))
|
|
590
|
+
return true;
|
|
591
|
+
if (stmt.type === 'ForOfStatement' && hasJSXInSubtree(stmt))
|
|
592
|
+
return true;
|
|
593
|
+
if (stmt.type === 'ForStatement' && hasJSXInSubtree(stmt))
|
|
594
|
+
return true;
|
|
595
|
+
if (stmt.type === 'ForInStatement' && hasJSXInSubtree(stmt))
|
|
596
|
+
return true;
|
|
597
|
+
if (stmt.type === 'WhileStatement' && hasJSXInSubtree(stmt))
|
|
598
|
+
return true;
|
|
599
|
+
if (stmt.type === 'DoWhileStatement' && hasJSXInSubtree(stmt))
|
|
600
|
+
return true;
|
|
601
|
+
if (stmt.type === 'SwitchStatement' && hasJSXInSubtree(stmt))
|
|
602
|
+
return true;
|
|
603
|
+
if (stmt.type === 'TryStatement' && hasJSXInSubtree(stmt))
|
|
604
|
+
return true;
|
|
605
|
+
if (stmt.type === 'LabeledStatement' && hasJSXInSubtree(stmt))
|
|
606
|
+
return true;
|
|
607
|
+
}
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
function processBlockBody(source, block) {
|
|
611
|
+
if (block.type === 'BlockStatement')
|
|
612
|
+
return processStatementModeBody(source, block.body);
|
|
613
|
+
if (block.type === 'JSXElement')
|
|
614
|
+
return processJSXElement(source, block);
|
|
615
|
+
if (block.type === 'JSXFragment') {
|
|
616
|
+
const nodes = [];
|
|
617
|
+
for (const c of block.children)
|
|
618
|
+
nodes.push(...processJSXChildren(source, [c]));
|
|
619
|
+
return nodes;
|
|
620
|
+
}
|
|
621
|
+
if (block.type === 'IfStatement')
|
|
622
|
+
return processIfStatement(source, block);
|
|
623
|
+
if (block.type === 'JSXExpressionContainer') {
|
|
624
|
+
return exprToIR(source, block.expression);
|
|
625
|
+
}
|
|
626
|
+
const raw = getSource(source, block);
|
|
627
|
+
if (raw)
|
|
628
|
+
return [new RuntimeStatement(raw, block, source)];
|
|
629
|
+
return [];
|
|
630
|
+
}
|
|
631
|
+
function processIfStatement(source, stmt) {
|
|
632
|
+
const condExpr = toExpression(source, stmt.test);
|
|
633
|
+
const consequent = processBlockBody(source, stmt.consequent);
|
|
634
|
+
const alternate = stmt.alternate ? processBlockBody(source, stmt.alternate) : [];
|
|
635
|
+
return [new OpaqueDynamicRegion(condExpr, consequent, alternate)];
|
|
636
|
+
}
|
|
637
|
+
function processForStatement(source, stmt, alternate = []) {
|
|
638
|
+
if (stmt.type === 'ForOfStatement') {
|
|
639
|
+
const left = stmt.left;
|
|
640
|
+
const itemVar = left.type === 'VariableDeclaration'
|
|
641
|
+
? left.declarations[0]?.id?.name ?? 'item'
|
|
642
|
+
: left.name ?? 'item';
|
|
643
|
+
const arrayExpr = toExpression(source, stmt.right);
|
|
644
|
+
const bodyTemplate = processBlockBody(source, stmt.body);
|
|
645
|
+
const ann = getForClauseAnnotation(stmt.start);
|
|
646
|
+
const keyExpr = ann?.keyRange ? new Expression(source.slice(ann.keyRange[0], ann.keyRange[1])) : null;
|
|
647
|
+
const indexVar = ann?.indexName ?? null;
|
|
648
|
+
return [new MapRegion(arrayExpr, itemVar, bodyTemplate, keyExpr, indexVar, alternate)];
|
|
649
|
+
}
|
|
650
|
+
if (stmt.type === 'ForInStatement') {
|
|
651
|
+
const left = getSource(source, stmt.left);
|
|
652
|
+
const objExpr = toExpression(source, stmt.right);
|
|
653
|
+
const bodyTemplate = processBlockBody(source, stmt.body);
|
|
654
|
+
return [new ForLoop(left, objExpr, '', bodyTemplate, 'for-in')];
|
|
655
|
+
}
|
|
656
|
+
if (stmt.type === 'ForStatement') {
|
|
657
|
+
const init = stmt.init ? getSource(source, stmt.init) : '';
|
|
658
|
+
const test = stmt.test ? toExpression(source, stmt.test) : new Expression('true');
|
|
659
|
+
const update = stmt.update ? getSource(source, stmt.update) : '';
|
|
660
|
+
const bodyTemplate = processBlockBody(source, stmt.body);
|
|
661
|
+
return [new ForLoop(init, test, update, bodyTemplate, 'for')];
|
|
662
|
+
}
|
|
663
|
+
return [];
|
|
664
|
+
}
|
|
665
|
+
function processWhileStatement(source, stmt) {
|
|
666
|
+
const condition = toExpression(source, stmt.test);
|
|
667
|
+
const bodyTemplate = processBlockBody(source, stmt.body);
|
|
668
|
+
const isDoWhile = stmt.type === 'DoWhileStatement';
|
|
669
|
+
return [new WhileLoop(condition, bodyTemplate, isDoWhile)];
|
|
670
|
+
}
|
|
671
|
+
function processSwitchStatement(source, stmt) {
|
|
672
|
+
const discriminant = toExpression(source, stmt.discriminant);
|
|
673
|
+
const cases = stmt.cases.map((c) => ({
|
|
674
|
+
test: c.test ? toExpression(source, c.test) : null,
|
|
675
|
+
body: processStatementModeBody(source, c.consequent),
|
|
676
|
+
}));
|
|
677
|
+
return [new SwitchBlock(discriminant, cases)];
|
|
678
|
+
}
|
|
679
|
+
function processTryStatement(source, stmt) {
|
|
680
|
+
const bodyTemplate = processBlockBody(source, stmt.block);
|
|
681
|
+
const catchBody = stmt.handler ? processBlockBody(source, stmt.handler.body) : [];
|
|
682
|
+
const catchParamName = stmt.handler?.param?.name ?? null;
|
|
683
|
+
return [new TryCatch(bodyTemplate, catchBody, catchParamName)];
|
|
684
|
+
}
|
|
685
|
+
function processStatementModeBody(source, bodyStmts) {
|
|
686
|
+
const nodes = [];
|
|
687
|
+
for (let i = 0; i < bodyStmts.length; i++) {
|
|
688
|
+
const stmt = bodyStmts[i];
|
|
689
|
+
if (stmt.type === 'JSXElement') {
|
|
690
|
+
nodes.push(...processJSXElement(source, stmt));
|
|
691
|
+
}
|
|
692
|
+
else if (stmt.type === 'JSXExpressionContainer') {
|
|
693
|
+
if (stmt.expression.type === 'JSXEmptyExpression')
|
|
694
|
+
continue;
|
|
695
|
+
if (isMapCall(stmt.expression)) {
|
|
696
|
+
const arrowFn = stmt.expression.arguments[0];
|
|
697
|
+
const itemVar = arrowFn.params[0]?.name ?? 'item';
|
|
698
|
+
const bodyNodes = processJSXCallbackBody(source, arrowFn.body);
|
|
699
|
+
const arrayExpr = toExpression(source, stmt.expression.callee.object);
|
|
700
|
+
const keyExpr = extractKeyExpr(bodyNodes);
|
|
701
|
+
nodes.push(new MapRegion(arrayExpr, itemVar, bodyNodes, keyExpr));
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
nodes.push(...exprToIR(source, stmt.expression));
|
|
705
|
+
}
|
|
706
|
+
else if (stmt.type === 'JSXFragment') {
|
|
707
|
+
for (const c of stmt.children) {
|
|
708
|
+
nodes.push(...processJSXChildren(source, [c]));
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
else if (stmt.type === 'VeskBlock') {
|
|
712
|
+
if (stmt.tag === 'empty')
|
|
713
|
+
continue;
|
|
714
|
+
const inner = processStatementModeBody(source, stmt.body);
|
|
715
|
+
if (stmt.tag === 'server') {
|
|
716
|
+
nodes.push(new ServerBlock(inner));
|
|
717
|
+
}
|
|
718
|
+
else if (stmt.tag === 'client') {
|
|
719
|
+
nodes.push(new ClientBlock(inner));
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
else if (isTrackDeclaration(stmt)) {
|
|
723
|
+
const elements = stmt.declarations[0].id.elements;
|
|
724
|
+
const name = elements[0]?.name;
|
|
725
|
+
const rawName = elements.length > 1 ? elements[1]?.name : null;
|
|
726
|
+
const init = getSource(source, stmt.declarations[0].init);
|
|
727
|
+
if (name)
|
|
728
|
+
nodes.push(new TrackDecl(name, init, rawName));
|
|
729
|
+
const refName = getComponentRefName(stmt);
|
|
730
|
+
if (refName)
|
|
731
|
+
nodes.push(new ComponentRef(refName));
|
|
732
|
+
}
|
|
733
|
+
else if (stmt.type === 'IfStatement') {
|
|
734
|
+
nodes.push(...processIfStatement(source, stmt));
|
|
735
|
+
}
|
|
736
|
+
else if (stmt.type === 'ForOfStatement') {
|
|
737
|
+
let alternate = [];
|
|
738
|
+
let consumed = 1;
|
|
739
|
+
const next = bodyStmts[i + 1];
|
|
740
|
+
if (next && next.type === 'VeskBlock' && next.tag === 'empty') {
|
|
741
|
+
alternate = processStatementModeBody(source, next.body);
|
|
742
|
+
consumed = 2;
|
|
743
|
+
}
|
|
744
|
+
nodes.push(...processForStatement(source, stmt, alternate));
|
|
745
|
+
i += consumed - 1;
|
|
746
|
+
}
|
|
747
|
+
else if (stmt.type === 'WhileStatement' || stmt.type === 'DoWhileStatement') {
|
|
748
|
+
nodes.push(...processWhileStatement(source, stmt));
|
|
749
|
+
}
|
|
750
|
+
else if (stmt.type === 'SwitchStatement') {
|
|
751
|
+
nodes.push(...processSwitchStatement(source, stmt));
|
|
752
|
+
}
|
|
753
|
+
else if (stmt.type === 'TryStatement') {
|
|
754
|
+
nodes.push(...processTryStatement(source, stmt));
|
|
755
|
+
}
|
|
756
|
+
else if (stmt.type === 'ReturnStatement') {
|
|
757
|
+
if (stmt.argument) {
|
|
758
|
+
nodes.push(...exprToIR(source, stmt.argument));
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
else if (stmt.type === 'LabeledStatement') {
|
|
762
|
+
nodes.push(...processBlockBody(source, stmt.body));
|
|
763
|
+
}
|
|
764
|
+
else if (stmt.type === 'ForInStatement') {
|
|
765
|
+
nodes.push(...processForStatement(source, stmt));
|
|
766
|
+
}
|
|
767
|
+
else if (stmt.type === 'ForStatement') {
|
|
768
|
+
nodes.push(...processForStatement(source, stmt));
|
|
769
|
+
}
|
|
770
|
+
else if (stmt.type === 'ClassDeclaration') {
|
|
771
|
+
throw VeskError.classDecl();
|
|
772
|
+
}
|
|
773
|
+
else {
|
|
774
|
+
const raw = getSource(source, stmt);
|
|
775
|
+
if (raw)
|
|
776
|
+
nodes.push(new RuntimeStatement(raw, stmt, source));
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return nodes;
|
|
780
|
+
}
|
|
781
|
+
function extractStyle(body) {
|
|
782
|
+
const cssParts = [];
|
|
783
|
+
const filtered = [];
|
|
784
|
+
for (const node of body) {
|
|
785
|
+
if (node instanceof StaticNode && node.tag === 'style') {
|
|
786
|
+
for (const child of node.children) {
|
|
787
|
+
if (child instanceof TextNode) {
|
|
788
|
+
cssParts.push(child.value);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
else {
|
|
793
|
+
filtered.push(node);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return { body: filtered, css: cssParts.join('\n') || null };
|
|
797
|
+
}
|
|
798
|
+
function validateBlocks(compName, isClient, body) {
|
|
799
|
+
for (const node of body) {
|
|
800
|
+
if (isClient) {
|
|
801
|
+
if (node instanceof ServerBlock) {
|
|
802
|
+
throw VeskError.serverBlockInClient(compName);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
else {
|
|
806
|
+
if (node instanceof ClientBlock) {
|
|
807
|
+
throw VeskError.clientBlockInServer(compName);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
if (node instanceof StaticNode || node instanceof ServerBlock || node instanceof ClientBlock) {
|
|
811
|
+
validateBlocks(compName, isClient, node.children || []);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
function processEnum(node, source, exported) {
|
|
816
|
+
const name = node.id.name;
|
|
817
|
+
const pairs = [];
|
|
818
|
+
const reversePairs = [];
|
|
819
|
+
let autoVal = 0;
|
|
820
|
+
for (const member of node.members) {
|
|
821
|
+
const key = member.id.name;
|
|
822
|
+
let val;
|
|
823
|
+
if (member.initializer) {
|
|
824
|
+
val = getSource(source, member.initializer);
|
|
825
|
+
}
|
|
826
|
+
else {
|
|
827
|
+
val = String(autoVal);
|
|
828
|
+
}
|
|
829
|
+
pairs.push(`${JSON.stringify(key)}: ${val}`);
|
|
830
|
+
reversePairs.push(`${val}: ${JSON.stringify(key)}`);
|
|
831
|
+
if (!member.initializer)
|
|
832
|
+
autoVal++;
|
|
833
|
+
}
|
|
834
|
+
const allPairs = [...reversePairs, ...pairs].join(', ');
|
|
835
|
+
const prefix = exported ? `export const ${name}` : `const ${name}`;
|
|
836
|
+
return `${prefix} = { ${allPairs} };`;
|
|
837
|
+
}
|
|
838
|
+
export function generateIR(ast, source) {
|
|
839
|
+
__vskAnnotations = ast.__vskAnnotations ?? [];
|
|
840
|
+
const components = [];
|
|
841
|
+
const imports = [];
|
|
842
|
+
const importedNames = new Set();
|
|
843
|
+
let staticProps = null;
|
|
844
|
+
let loadFn = null;
|
|
845
|
+
const topLevelCode = [];
|
|
846
|
+
for (const node of ast.body) {
|
|
847
|
+
if (node.type === 'ImportDeclaration') {
|
|
848
|
+
const raw = getSource(source, node);
|
|
849
|
+
const cleaned = stripTypeImport(raw);
|
|
850
|
+
if (cleaned === null)
|
|
851
|
+
continue;
|
|
852
|
+
imports.push(cleaned);
|
|
853
|
+
for (const spec of node.specifiers) {
|
|
854
|
+
if (spec.importKind === 'type')
|
|
855
|
+
continue;
|
|
856
|
+
if (spec.type === 'ImportSpecifier') {
|
|
857
|
+
importedNames.add(spec.local.name);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
if (node.type === 'ExportNamedDeclaration' && node.declaration && !staticProps) {
|
|
863
|
+
const decl = node.declaration;
|
|
864
|
+
const fnName = decl.type === 'FunctionDeclaration'
|
|
865
|
+
? decl.id?.name
|
|
866
|
+
: decl.type === 'VariableDeclaration'
|
|
867
|
+
? decl.declarations[0]?.id?.name
|
|
868
|
+
: null;
|
|
869
|
+
if (fnName === 'getStaticProps') {
|
|
870
|
+
staticProps = stripCodeTypes(getSource(source, decl));
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
if (node.type === 'ExportNamedDeclaration' && node.declaration && !loadFn) {
|
|
875
|
+
const decl = node.declaration;
|
|
876
|
+
const fnName = decl.type === 'FunctionDeclaration'
|
|
877
|
+
? decl.id?.name
|
|
878
|
+
: decl.type === 'VariableDeclaration'
|
|
879
|
+
? decl.declarations[0]?.id?.name
|
|
880
|
+
: null;
|
|
881
|
+
if (fnName === 'load') {
|
|
882
|
+
loadFn = stripCodeTypes(getSource(source, decl));
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
let inner = node;
|
|
887
|
+
let exported = false;
|
|
888
|
+
let defaultExport = false;
|
|
889
|
+
if (node.type === 'ExportNamedDeclaration' && node.declaration) {
|
|
890
|
+
inner = node.declaration;
|
|
891
|
+
exported = true;
|
|
892
|
+
}
|
|
893
|
+
else if (node.type === 'ExportDefaultDeclaration' && node.declaration) {
|
|
894
|
+
inner = node.declaration;
|
|
895
|
+
exported = true;
|
|
896
|
+
defaultExport = true;
|
|
897
|
+
}
|
|
898
|
+
if (inner.type === 'ComponentDeclaration') {
|
|
899
|
+
// handled below
|
|
900
|
+
}
|
|
901
|
+
else if (inner.type === 'ClassDeclaration') {
|
|
902
|
+
throw VeskError.classDecl();
|
|
903
|
+
}
|
|
904
|
+
else if (inner.type === 'TSEnumDeclaration') {
|
|
905
|
+
const code = processEnum(inner, source, exported);
|
|
906
|
+
topLevelCode.push(code);
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
else {
|
|
910
|
+
topLevelCode.push(getSource(source, node));
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
const name = inner.id.name;
|
|
914
|
+
const paramNames = getParamNames(inner.params, source);
|
|
915
|
+
const propsType = getPropsType(inner.params, source);
|
|
916
|
+
const bodyStmts = inner.body.body;
|
|
917
|
+
const isClientComp = !!inner.client;
|
|
918
|
+
if (isStatementMode(bodyStmts)) {
|
|
919
|
+
const raw = processStatementModeBody(source, bodyStmts);
|
|
920
|
+
const { body, css } = extractStyle(raw);
|
|
921
|
+
validateBlocks(name, isClientComp, body);
|
|
922
|
+
const comp = new ComponentIR(name, paramNames, body, { mode: 'statement', exported, defaultExport, isClient: inner.client, isAsync: inner.async, ssrAwait: componentUsesFetch(body), propsType });
|
|
923
|
+
comp.style = css;
|
|
924
|
+
components.push(comp);
|
|
925
|
+
}
|
|
926
|
+
else {
|
|
927
|
+
const guardClauses = [];
|
|
928
|
+
let mainReturn = null;
|
|
929
|
+
const preamble = [];
|
|
930
|
+
for (const stmt of bodyStmts) {
|
|
931
|
+
if (stmt.type === 'VeskBlock') {
|
|
932
|
+
const innerBody = processStatementModeBody(source, stmt.body);
|
|
933
|
+
if (stmt.tag === 'server') {
|
|
934
|
+
preamble.push(new ServerBlock(innerBody));
|
|
935
|
+
}
|
|
936
|
+
else if (stmt.tag === 'client') {
|
|
937
|
+
preamble.push(new ClientBlock(innerBody));
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
else if (stmt.type === 'ReturnStatement') {
|
|
941
|
+
mainReturn = stmt;
|
|
942
|
+
}
|
|
943
|
+
else if (isTrackDeclaration(stmt)) {
|
|
944
|
+
const elements = stmt.declarations[0].id.elements;
|
|
945
|
+
const trackName = elements[0]?.name;
|
|
946
|
+
const rawName = elements.length > 1 ? elements[1]?.name : null;
|
|
947
|
+
const init = getSource(source, stmt.declarations[0].init);
|
|
948
|
+
if (trackName)
|
|
949
|
+
preamble.push(new TrackDecl(trackName, init, rawName));
|
|
950
|
+
const refName = getComponentRefName(stmt);
|
|
951
|
+
if (refName)
|
|
952
|
+
preamble.push(new ComponentRef(refName));
|
|
953
|
+
}
|
|
954
|
+
else if (stmt.type === 'IfStatement' && !mainReturn && stmt.consequent.type !== 'ThrowStatement') {
|
|
955
|
+
guardClauses.push(stmt);
|
|
956
|
+
}
|
|
957
|
+
else if (stmt.type === 'ClassDeclaration') {
|
|
958
|
+
throw VeskError.classDecl();
|
|
959
|
+
}
|
|
960
|
+
else {
|
|
961
|
+
const raw = getSource(source, stmt);
|
|
962
|
+
if (raw)
|
|
963
|
+
preamble.push(new RuntimeStatement(raw, stmt, source));
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
const guardBody = buildGuardChain(source, guardClauses, mainReturn);
|
|
967
|
+
const { body, css } = extractStyle([...preamble, ...guardBody]);
|
|
968
|
+
validateBlocks(name, isClientComp, body);
|
|
969
|
+
const comp = new ComponentIR(name, paramNames, body, { exported, defaultExport, isClient: inner.client, isAsync: inner.async, ssrAwait: componentUsesFetch(body), propsType });
|
|
970
|
+
comp.style = css;
|
|
971
|
+
components.push(comp);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
const asyncNames = new Set(components.filter((c) => c.isAsync || c.ssrAwait).map((c) => c.name));
|
|
975
|
+
for (const comp of components) {
|
|
976
|
+
if (asyncNames.has(comp.name))
|
|
977
|
+
continue;
|
|
978
|
+
const called = new Map();
|
|
979
|
+
collectComponentCalls(comp.body, called);
|
|
980
|
+
for (const [childName, start] of called) {
|
|
981
|
+
if (!asyncNames.has(childName))
|
|
982
|
+
continue;
|
|
983
|
+
const { line, column } = offsetToLineCol(source, start >= 0 ? start : 0);
|
|
984
|
+
throw VeskError.asyncChildInSyncParent(comp.name, childName, { line, column });
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
const autoImportable = [
|
|
988
|
+
'useFetch', 'useRouter', 'useParams', 'usePathname', 'useSearchParams', 'useNavigate',
|
|
989
|
+
'defineAction',
|
|
990
|
+
'Form', 'Field', 'required', 'email', 'minLength', 'maxLength', 'pattern', 'custom',
|
|
991
|
+
'Link', 'NavLink', 'Outlet', 'Redirect',
|
|
992
|
+
'Image', 'Portal',
|
|
993
|
+
'Experiment',
|
|
994
|
+
'JsonLd', 'ArticleSchema', 'ProductSchema', 'FAQPageSchema', 'BreadcrumbListSchema',
|
|
995
|
+
'OrganizationSchema', 'LocalBusinessSchema', 'VideoSchema',
|
|
996
|
+
'effect', 'derived', 'untrack', 'peek', 'tick', 'flushSync', 'on_destroy',
|
|
997
|
+
'createContext',
|
|
998
|
+
'redirect', 'permanentRedirect', 'notFound', 'NotFoundError',
|
|
999
|
+
'createResource', 'getAction', 'validateActionInput', 'issuesToFieldMap', 'isFormAction',
|
|
1000
|
+
];
|
|
1001
|
+
const usedFunctions = new Set();
|
|
1002
|
+
const addUsedFrom = (code) => {
|
|
1003
|
+
const called = collectCalledIdentifiers(code);
|
|
1004
|
+
for (const fn of autoImportable) {
|
|
1005
|
+
if (called.has(fn))
|
|
1006
|
+
usedFunctions.add(fn);
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
for (const code of topLevelCode)
|
|
1010
|
+
addUsedFrom(code);
|
|
1011
|
+
for (const code of [loadFn, staticProps]) {
|
|
1012
|
+
if (code)
|
|
1013
|
+
addUsedFrom(code);
|
|
1014
|
+
}
|
|
1015
|
+
function scanForAutoImport(nodes) {
|
|
1016
|
+
for (const node of nodes) {
|
|
1017
|
+
if (node instanceof RuntimeStatement && node.raw) {
|
|
1018
|
+
addUsedFrom(node.raw);
|
|
1019
|
+
}
|
|
1020
|
+
if (node instanceof ComponentCall) {
|
|
1021
|
+
if (autoImportable.includes(node.componentName)) {
|
|
1022
|
+
usedFunctions.add(node.componentName);
|
|
1023
|
+
}
|
|
1024
|
+
for (const prop of node.props) {
|
|
1025
|
+
if (prop.value && prop.value.raw) {
|
|
1026
|
+
addUsedFrom(prop.value.raw);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
scanForAutoImport(node.children);
|
|
1030
|
+
}
|
|
1031
|
+
if (node instanceof MapRegion) {
|
|
1032
|
+
scanForAutoImport(node.bodyTemplate);
|
|
1033
|
+
scanForAutoImport(node.alternateNodes);
|
|
1034
|
+
}
|
|
1035
|
+
if (node instanceof OpaqueDynamicRegion) {
|
|
1036
|
+
scanForAutoImport(node.consequentNodes);
|
|
1037
|
+
scanForAutoImport(node.alternateNodes);
|
|
1038
|
+
}
|
|
1039
|
+
if (node instanceof DynamicBinding && node.expression && node.expression.raw) {
|
|
1040
|
+
addUsedFrom(node.expression.raw);
|
|
1041
|
+
}
|
|
1042
|
+
if (node instanceof StaticNode || node instanceof ServerBlock || node instanceof ClientBlock || node instanceof HeadBlock) {
|
|
1043
|
+
scanForAutoImport(node.children);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
scanForAutoImport(components.flatMap(c => c.body));
|
|
1048
|
+
if (usedFunctions.size > 0) {
|
|
1049
|
+
const existing = new Set();
|
|
1050
|
+
for (const imp of imports) {
|
|
1051
|
+
if (importModuleTarget(imp) !== '@vesk/runtime')
|
|
1052
|
+
continue;
|
|
1053
|
+
for (const n of extractImportNames(imp))
|
|
1054
|
+
existing.add(n);
|
|
1055
|
+
}
|
|
1056
|
+
const missing = [...usedFunctions].filter(f => !existing.has(f));
|
|
1057
|
+
if (missing.length > 0) {
|
|
1058
|
+
imports.push(`import { ${missing.join(', ')} } from '@vesk/runtime';`);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
return new IRRoot(components, imports, importedNames, staticProps, loadFn, topLevelCode);
|
|
1062
|
+
}
|