@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.
Files changed (84) hide show
  1. package/README.md +23 -0
  2. package/dist/acorn-ts-plugin/index.d.ts +482 -0
  3. package/dist/acorn-ts-plugin/index.d.ts.map +1 -0
  4. package/dist/acorn-ts-plugin/index.js +5028 -0
  5. package/dist/actions.d.ts +22 -0
  6. package/dist/actions.d.ts.map +1 -0
  7. package/dist/actions.js +167 -0
  8. package/dist/api-routes.d.ts +30 -0
  9. package/dist/api-routes.d.ts.map +1 -0
  10. package/dist/api-routes.js +350 -0
  11. package/dist/client-codegen.d.ts +19 -0
  12. package/dist/client-codegen.d.ts.map +1 -0
  13. package/dist/client-codegen.js +1523 -0
  14. package/dist/config.d.ts +6 -0
  15. package/dist/config.d.ts.map +1 -0
  16. package/dist/config.js +106 -0
  17. package/dist/errors.d.ts +33 -0
  18. package/dist/errors.d.ts.map +1 -0
  19. package/dist/errors.js +210 -0
  20. package/dist/index.d.ts +16 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +8 -0
  23. package/dist/ir-generator.d.ts +10 -0
  24. package/dist/ir-generator.d.ts.map +1 -0
  25. package/dist/ir-generator.js +1062 -0
  26. package/dist/ir.d.ts +160 -0
  27. package/dist/ir.d.ts.map +1 -0
  28. package/dist/ir.js +219 -0
  29. package/dist/middleware.d.ts +15 -0
  30. package/dist/middleware.d.ts.map +1 -0
  31. package/dist/middleware.js +183 -0
  32. package/dist/package.json +15 -0
  33. package/dist/parser.d.ts +38 -0
  34. package/dist/parser.d.ts.map +1 -0
  35. package/dist/parser.js +242 -0
  36. package/dist/router.d.ts +31 -0
  37. package/dist/router.d.ts.map +1 -0
  38. package/dist/router.js +451 -0
  39. package/dist/scan.d.ts +106 -0
  40. package/dist/scan.d.ts.map +1 -0
  41. package/dist/scan.js +438 -0
  42. package/dist/server-codegen.d.ts +5 -0
  43. package/dist/server-codegen.d.ts.map +1 -0
  44. package/dist/server-codegen.js +4 -0
  45. package/dist/server-cookies.d.ts +2 -0
  46. package/dist/server-cookies.d.ts.map +1 -0
  47. package/dist/server-cookies.js +15 -0
  48. package/dist/server-head.d.ts +10 -0
  49. package/dist/server-head.d.ts.map +1 -0
  50. package/dist/server-head.js +289 -0
  51. package/dist/server-jsgen.d.ts +6 -0
  52. package/dist/server-jsgen.d.ts.map +1 -0
  53. package/dist/server-jsgen.js +446 -0
  54. package/dist/server-render.d.ts +16 -0
  55. package/dist/server-render.d.ts.map +1 -0
  56. package/dist/server-render.js +397 -0
  57. package/dist/server-utils.d.ts +93 -0
  58. package/dist/server-utils.d.ts.map +1 -0
  59. package/dist/server-utils.js +747 -0
  60. package/dist/ssr-store.d.ts +4 -0
  61. package/dist/ssr-store.d.ts.map +1 -0
  62. package/dist/ssr-store.js +29 -0
  63. package/dist/strip-ts.d.ts +25 -0
  64. package/dist/strip-ts.d.ts.map +1 -0
  65. package/dist/strip-ts.js +171 -0
  66. package/dist/tokens.d.ts +50 -0
  67. package/dist/tokens.d.ts.map +1 -0
  68. package/dist/tokens.js +334 -0
  69. package/dist/typecheck.d.ts +19 -0
  70. package/dist/typecheck.d.ts.map +1 -0
  71. package/dist/typecheck.js +365 -0
  72. package/dist/types.d.ts +147 -0
  73. package/dist/types.d.ts.map +1 -0
  74. package/dist/types.js +13 -0
  75. package/dist/vesk-plugin.d.ts +53 -0
  76. package/dist/vesk-plugin.d.ts.map +1 -0
  77. package/dist/vesk-plugin.js +413 -0
  78. package/dist/vsk-imports.d.ts +30 -0
  79. package/dist/vsk-imports.d.ts.map +1 -0
  80. package/dist/vsk-imports.js +141 -0
  81. package/dist/vsk-tsx.d.ts +3 -0
  82. package/dist/vsk-tsx.d.ts.map +1 -0
  83. package/dist/vsk-tsx.js +604 -0
  84. package/package.json +52 -0
@@ -0,0 +1,604 @@
1
+ import { parse } from '@vesk/compiler/src/parser';
2
+ import { getPropsType } from '@vesk/compiler/src/ir-generator';
3
+ import { skipWhitespace, findBalancedEnd, splitTopLevel, startsWithIdentifier, stripDeclKeyword, stripTrailingSemicolons, isWhitespaceChar, isIdentStart, isIdentChar, htmlTagEnd, collapseNewlineWhitespace, } from '@vesk/compiler/src/scan';
4
+ import { containsIdentifier, isIdentifierImported } from '@vesk/compiler/src/tokens';
5
+ function applyEdits(source, edits) {
6
+ const sorted = edits.sort((a, b) => b.start - a.start || b.end - a.end);
7
+ let out = source;
8
+ for (const e of sorted) {
9
+ out = out.slice(0, e.start) + e.text + out.slice(e.end);
10
+ }
11
+ return out;
12
+ }
13
+ /**
14
+ * Collects edit operations that rewrite `&[...] = track<...>(...)`
15
+ * declarations into plain typed `let` declarations, using the parsed AST so
16
+ * nested generics (`track<Map<string, number>>(...)`), type annotations and
17
+ * nested statement positions are handled without regex. `wrapped` is the
18
+ * fake component source (offsets inside it), `prefixLen` the distance from
19
+ * the start of `wrapped` to the start of the real body text.
20
+ */
21
+ function collectTrackDeclEdits(wrapped, ast, prefixLen) {
22
+ const edits = [];
23
+ function walk(stmts) {
24
+ for (const stmt of stmts) {
25
+ if (!stmt)
26
+ continue;
27
+ if (stmt.type === 'VariableDeclaration') {
28
+ const decls = stmt.declarations || [];
29
+ for (let di = 0; di < decls.length; di++) {
30
+ const decl = decls[di];
31
+ const id = decl.id;
32
+ if (!id || id.type !== 'ArrayPattern' || id.lazy !== true)
33
+ continue;
34
+ if (!decl.init)
35
+ continue;
36
+ const names = (id.elements || [])
37
+ .map((el) => (el ? el.name : null))
38
+ .filter((n) => typeof n === 'string');
39
+ const initText = wrapped.slice(decl.init.start, decl.init.end);
40
+ // The first declarator's replacement must also absorb the statement's
41
+ // `const`/`let`/`var` keyword, which starts at `stmt.start`, not at
42
+ // the declarator's start.
43
+ const start = di === 0 ? stmt.start : decl.start;
44
+ const end = decl.end;
45
+ // Don't duplicate the statement terminator already present after the
46
+ // declaration (avoids `let x = ...;;`).
47
+ const terminator = wrapped[end] === ';' ? '' : ';';
48
+ if (names.length === 0) {
49
+ edits.push({ start: start - prefixLen, end: end - prefixLen, text: `let _ = ${initText}${terminator}` });
50
+ continue;
51
+ }
52
+ const annotation = id.typeAnnotation?.typeAnnotation
53
+ ? wrapped.slice(id.typeAnnotation.typeAnnotation.start, id.typeAnnotation.typeAnnotation.end).trim()
54
+ : decl.init.typeArguments
55
+ ? wrapped.slice(decl.init.typeArguments.start + 1, decl.init.typeArguments.end - 1).trim()
56
+ : '';
57
+ const first = names[0];
58
+ let replacement;
59
+ if (annotation) {
60
+ replacement = `let ${first}: ${annotation} = (${initText} as unknown as ${annotation});`;
61
+ }
62
+ else {
63
+ replacement = `let ${first}: any = ${initText};`;
64
+ }
65
+ for (let n = 1; n < names.length; n++) {
66
+ replacement += ` let ${names[n]}: any = ${first};`;
67
+ }
68
+ // The last emitted statement sits directly before `end`; drop its
69
+ // terminator when the source already provides one (avoids `;;`).
70
+ if (wrapped[end] === ';')
71
+ replacement = replacement.slice(0, -1);
72
+ edits.push({ start: start - prefixLen, end: end - prefixLen, text: replacement });
73
+ }
74
+ continue;
75
+ }
76
+ const kids = [];
77
+ if (stmt.consequent)
78
+ kids.push(stmt.consequent);
79
+ if (stmt.alternate)
80
+ kids.push(stmt.alternate);
81
+ if (Array.isArray(stmt.body))
82
+ kids.push(...stmt.body);
83
+ else if (stmt.body && stmt.body.type)
84
+ kids.push(stmt.body);
85
+ if (Array.isArray(stmt.cases)) {
86
+ for (const c of stmt.cases)
87
+ if (Array.isArray(c.consequent))
88
+ kids.push(...c.consequent);
89
+ }
90
+ if (stmt.handler)
91
+ kids.push(stmt.handler.body);
92
+ if (stmt.finalizer)
93
+ kids.push(stmt.finalizer.body);
94
+ walk(kids);
95
+ }
96
+ }
97
+ walk(ast.body);
98
+ return edits;
99
+ }
100
+ const REWRITER_WRAPPER = 'component __VskRewriter() { ';
101
+ const REWRITER_WRAPPER_LEN = REWRITER_WRAPPER.length;
102
+ function rewriteTrackDecls(text) {
103
+ if (!text.includes('&['))
104
+ return text;
105
+ const wrapped = REWRITER_WRAPPER + text + ' }';
106
+ let ast;
107
+ try {
108
+ ast = parse(wrapped);
109
+ }
110
+ catch {
111
+ return text;
112
+ }
113
+ const edits = collectTrackDeclEdits(wrapped, ast, REWRITER_WRAPPER_LEN);
114
+ if (edits.length === 0)
115
+ return text;
116
+ return applyEdits(text, edits);
117
+ }
118
+ function stripStyleBlocks(text) {
119
+ const out = [];
120
+ let i = 0;
121
+ while (i < text.length) {
122
+ if (text[i] === '<' && text.slice(i, i + 6) === '<style') {
123
+ const next = i + 6;
124
+ if (next >= text.length || isWhitespaceChar(text[next]) || text[next] === '>' || text[next] === '/') {
125
+ const openEnd = htmlTagEnd(text, i);
126
+ if (openEnd !== -1) {
127
+ const closeIdx = text.indexOf('</style>', openEnd);
128
+ if (closeIdx !== -1) {
129
+ i = closeIdx + '</style>'.length;
130
+ continue;
131
+ }
132
+ }
133
+ }
134
+ }
135
+ out.push(text[i]);
136
+ i++;
137
+ }
138
+ return out.join('');
139
+ }
140
+ function getComponents(ast) {
141
+ const out = [];
142
+ for (const stmt of ast.body || []) {
143
+ if (stmt.type === 'ComponentDeclaration') {
144
+ out.push({ node: stmt, exported: false, defaultExport: false, start: stmt.start });
145
+ }
146
+ else if (stmt.type === 'ExportNamedDeclaration' && stmt.declaration?.type === 'ComponentDeclaration') {
147
+ out.push({ node: stmt.declaration, exported: true, defaultExport: false, start: stmt.start });
148
+ }
149
+ else if (stmt.type === 'ExportDefaultDeclaration' && stmt.declaration?.type === 'ComponentDeclaration') {
150
+ out.push({ node: stmt.declaration, exported: true, defaultExport: true, start: stmt.start });
151
+ }
152
+ }
153
+ return out;
154
+ }
155
+ function blankForClauses(source, ast) {
156
+ const annotations = ast.__vskAnnotations ?? [];
157
+ let out = source;
158
+ for (const ann of annotations) {
159
+ if (ann.kind !== 'for-clause')
160
+ continue;
161
+ out = out.slice(0, ann.clauseStart) + ' '.repeat(ann.clauseEnd - ann.clauseStart) + out.slice(ann.clauseEnd);
162
+ }
163
+ return out;
164
+ }
165
+ function extractForHeader(text) {
166
+ if (!startsWithIdentifier(text, 'for'))
167
+ return null;
168
+ const i = skipWhitespace(text, 3);
169
+ if (text[i] !== '(')
170
+ return null;
171
+ const end = findBalancedEnd(text, i);
172
+ return text.slice(i + 1, end);
173
+ }
174
+ function getJSXTagName(el) {
175
+ const name = el?.openingElement?.name ?? el?.name;
176
+ if (!name)
177
+ return null;
178
+ if (name.type === 'JSXIdentifier')
179
+ return name.name;
180
+ if (name.type === 'JSXMemberExpression')
181
+ return name.object.name + '.' + name.property.name;
182
+ if (name.type === 'JSXNamespacedName')
183
+ return name.namespace.name + ':' + name.name.name;
184
+ return null;
185
+ }
186
+ function tsxBlock(source, body, indent) {
187
+ const stmts = body && body.type === 'BlockStatement'
188
+ ? body.body
189
+ : body ? (Array.isArray(body) ? body : [body]) : [];
190
+ const inner = tsxEmitBody(source, stmts, indent + ' ');
191
+ return `{\n${inner}\n${indent}}`;
192
+ }
193
+ function tsxEmitJSXChildren(source, children) {
194
+ const parts = [];
195
+ let i = 0;
196
+ let forPending = false;
197
+ while (i < children.length) {
198
+ const child = children[i];
199
+ if (child.type !== 'JSXText')
200
+ forPending = false;
201
+ if (child.type === 'JSXText') {
202
+ const text = collapseNewlineWhitespace(child.value);
203
+ const trimmed = text.trim();
204
+ if (!trimmed || trimmed.startsWith('//')) {
205
+ i++;
206
+ continue;
207
+ }
208
+ const forDecl = extractForHeader(trimmed);
209
+ const nextChild = children[i + 1];
210
+ if (forDecl !== null && nextChild !== undefined && (nextChild.type === 'JSXExpressionContainer' ||
211
+ nextChild.type === 'JSXElement' ||
212
+ nextChild.type === 'JSXFragment')) {
213
+ let bodyText;
214
+ if (nextChild.type === 'JSXExpressionContainer') {
215
+ const exprNode = nextChild.expression;
216
+ if (exprNode.type === 'JSXEmptyExpression') {
217
+ parts.push(text);
218
+ i++;
219
+ continue;
220
+ }
221
+ bodyText = tsxEmitJSXExprBody(source, exprNode);
222
+ }
223
+ else if (nextChild.type === 'JSXFragment') {
224
+ bodyText = tsxEmitJSXFragment(source, nextChild);
225
+ }
226
+ else {
227
+ bodyText = tsxEmitJSXElement(source, nextChild);
228
+ }
229
+ let emptyText = '';
230
+ let consumed = 2;
231
+ const emptyTextNode = children[i + 2];
232
+ const emptyContainer = children[i + 3];
233
+ if (emptyTextNode && emptyTextNode.type === 'JSXText' &&
234
+ ['#empty', 'empty'].includes(emptyTextNode.value.trim()) &&
235
+ emptyContainer && emptyContainer.type === 'JSXExpressionContainer' &&
236
+ emptyContainer.expression.type !== 'JSXEmptyExpression') {
237
+ emptyText = tsxEmitJSXExprBody(source, emptyContainer.expression);
238
+ consumed = 4;
239
+ }
240
+ const ofParts = splitTopLevel(forDecl, 'of');
241
+ if (ofParts.length === 2) {
242
+ const itemVar = stripDeclKeyword(ofParts[0]).trim();
243
+ const arrExpr = ofParts[1].trim();
244
+ const map = `{ ${arrExpr}.map((${itemVar}: (typeof ${arrExpr})[number]) => (${bodyText})) }`;
245
+ if (emptyText) {
246
+ parts.push(`{ (() => { const _v = ${arrExpr}; return _v.length === 0 ? (${emptyText}) : _v.map((${itemVar}: (typeof _v)[number]) => (${bodyText})); })() }`);
247
+ }
248
+ else {
249
+ parts.push(map);
250
+ }
251
+ i += consumed;
252
+ forPending = true;
253
+ continue;
254
+ }
255
+ const inParts = splitTopLevel(forDecl, 'in');
256
+ if (inParts.length === 2) {
257
+ const itemVar = stripDeclKeyword(inParts[0]).trim();
258
+ const objExpr = inParts[1].trim();
259
+ parts.push(`{ Object.keys(${objExpr}).map((${itemVar}) => (${bodyText})) }`);
260
+ i += consumed;
261
+ forPending = true;
262
+ continue;
263
+ }
264
+ }
265
+ if (forPending && trimmed === '}') {
266
+ forPending = false;
267
+ i++;
268
+ continue;
269
+ }
270
+ forPending = false;
271
+ parts.push(text);
272
+ i++;
273
+ }
274
+ else if (child.type === 'JSXExpressionContainer') {
275
+ if (child.expression.type === 'JSXEmptyExpression') {
276
+ i++;
277
+ continue;
278
+ }
279
+ parts.push(`{${source.slice(child.expression.start, child.expression.end)}}`);
280
+ i++;
281
+ }
282
+ else if (child.type === 'JSXElement') {
283
+ if (getJSXTagName(child) === 'style') {
284
+ i++;
285
+ continue;
286
+ }
287
+ parts.push(tsxEmitJSXElement(source, child));
288
+ i++;
289
+ }
290
+ else if (child.type === 'JSXFragment') {
291
+ parts.push(tsxEmitJSXFragment(source, child));
292
+ i++;
293
+ }
294
+ else {
295
+ i++;
296
+ }
297
+ }
298
+ return parts.join('');
299
+ }
300
+ function tsxEmitJSXExprBody(source, expr) {
301
+ if (expr.type === 'JSXElement')
302
+ return tsxEmitJSXElement(source, expr);
303
+ if (expr.type === 'JSXFragment')
304
+ return tsxEmitJSXFragment(source, expr);
305
+ return source.slice(expr.start, expr.end);
306
+ }
307
+ function tsxEmitJSXElement(source, el) {
308
+ const opening = source.slice(el.openingElement.start, el.openingElement.end);
309
+ if (el.openingElement.selfClosing)
310
+ return opening;
311
+ const inner = tsxEmitJSXChildren(source, el.children ?? []);
312
+ const closing = el.closingElement ? source.slice(el.closingElement.start, el.closingElement.end) : '';
313
+ return opening + inner + closing;
314
+ }
315
+ function tsxEmitJSXFragment(source, frag) {
316
+ const inner = tsxEmitJSXChildren(source, frag.children ?? []);
317
+ return '<>' + inner + '</>';
318
+ }
319
+ function tsxEmitIf(source, stmt, indent) {
320
+ const cond = source.slice(stmt.test.start, stmt.test.end);
321
+ let out = `${indent}if (${cond}) ${tsxBlock(source, stmt.consequent, indent)}`;
322
+ if (stmt.alternate) {
323
+ const altBlock = stmt.alternate.type === 'BlockStatement'
324
+ ? tsxBlock(source, stmt.alternate, indent)
325
+ : tsxBlock(source, [stmt.alternate], indent);
326
+ out += ` else ${altBlock}`;
327
+ }
328
+ return out;
329
+ }
330
+ function tsxEmitForOf(source, stmt, indent) {
331
+ const left = source.slice(stmt.left.start, stmt.left.end);
332
+ const right = source.slice(stmt.right.start, stmt.right.end);
333
+ return `${indent}for (${left} of ${right}) ${tsxBlock(source, stmt.body, indent)}`;
334
+ }
335
+ function tsxEmitForIn(source, stmt, indent) {
336
+ const left = source.slice(stmt.left.start, stmt.left.end);
337
+ const right = source.slice(stmt.right.start, stmt.right.end);
338
+ return `${indent}for (${left} in ${right}) ${tsxBlock(source, stmt.body, indent)}`;
339
+ }
340
+ function tsxEmitFor(source, stmt, indent) {
341
+ const init = stmt.init ? source.slice(stmt.init.start, stmt.init.end) : '';
342
+ const test = stmt.test ? source.slice(stmt.test.start, stmt.test.end) : '';
343
+ const update = stmt.update ? source.slice(stmt.update.start, stmt.update.end) : '';
344
+ return `${indent}for (${init}; ${test}; ${update}) ${tsxBlock(source, stmt.body, indent)}`;
345
+ }
346
+ function tsxEmitWhile(source, stmt, indent) {
347
+ const test = source.slice(stmt.test.start, stmt.test.end);
348
+ return `${indent}while (${test}) ${tsxBlock(source, stmt.body, indent)}`;
349
+ }
350
+ function tsxEmitDoWhile(source, stmt, indent) {
351
+ const test = source.slice(stmt.test.start, stmt.test.end);
352
+ return `${indent}do ${tsxBlock(source, stmt.body, indent)} while (${test});`;
353
+ }
354
+ function tsxEmitSwitch(source, stmt, indent) {
355
+ const disc = source.slice(stmt.discriminant.start, stmt.discriminant.end);
356
+ const lines = [`${indent}switch (${disc}) {`];
357
+ for (const c of stmt.cases ?? []) {
358
+ if (c.test) {
359
+ lines.push(`${indent} case ${source.slice(c.test.start, c.test.end)}:`);
360
+ }
361
+ else {
362
+ lines.push(`${indent} default:`);
363
+ }
364
+ const bodyText = tsxEmitBody(source, c.consequent, indent + ' ');
365
+ if (bodyText)
366
+ lines.push(bodyText);
367
+ }
368
+ lines.push(`${indent}}`);
369
+ return lines.join('\n');
370
+ }
371
+ function tsxEmitTry(source, stmt, indent) {
372
+ let out = `${indent}try ${tsxBlock(source, stmt.block, indent)}`;
373
+ if (stmt.handler) {
374
+ const param = stmt.handler.param ? source.slice(stmt.handler.param.start, stmt.handler.param.end) : '';
375
+ out += ` catch${param ? ` (${param})` : ''} ${tsxBlock(source, stmt.handler.body, indent)}`;
376
+ }
377
+ if (stmt.finalizer) {
378
+ out += ` finally ${tsxBlock(source, stmt.finalizer, indent)}`;
379
+ }
380
+ return out;
381
+ }
382
+ function tsxEmitStatement(source, stmt, indent, isLast) {
383
+ switch (stmt.type) {
384
+ case 'JSXElement':
385
+ if (getJSXTagName(stmt) === 'style')
386
+ return '';
387
+ return indent + tsxEmitJSXElement(source, stmt) + (isLast ? '' : ';');
388
+ case 'JSXFragment':
389
+ return indent + tsxEmitJSXFragment(source, stmt) + (isLast ? '' : ';');
390
+ case 'JSXExpressionContainer': {
391
+ if (stmt.expression.type === 'JSXEmptyExpression')
392
+ return '';
393
+ const text = `{${source.slice(stmt.expression.start, stmt.expression.end)}}`;
394
+ return indent + text + (isLast ? '' : ';');
395
+ }
396
+ case 'VeskBlock': {
397
+ if (stmt.tag === 'empty')
398
+ return '';
399
+ const lines = [];
400
+ const innerStmts = stmt.body ?? [];
401
+ for (let i = 0; i < innerStmts.length; i++) {
402
+ const t = tsxEmitStatement(source, innerStmts[i], indent, isLast && i === innerStmts.length - 1);
403
+ if (t !== '')
404
+ lines.push(t);
405
+ }
406
+ return lines.join('\n');
407
+ }
408
+ case 'IfStatement':
409
+ return tsxEmitIf(source, stmt, indent);
410
+ case 'ForOfStatement':
411
+ return tsxEmitForOf(source, stmt, indent);
412
+ case 'ForInStatement':
413
+ return tsxEmitForIn(source, stmt, indent);
414
+ case 'ForStatement':
415
+ return tsxEmitFor(source, stmt, indent);
416
+ case 'WhileStatement':
417
+ return tsxEmitWhile(source, stmt, indent);
418
+ case 'DoWhileStatement':
419
+ return tsxEmitDoWhile(source, stmt, indent);
420
+ case 'SwitchStatement':
421
+ return tsxEmitSwitch(source, stmt, indent);
422
+ case 'TryStatement':
423
+ return tsxEmitTry(source, stmt, indent);
424
+ case 'LabeledStatement': {
425
+ const body = tsxEmitStatement(source, stmt.body, indent, isLast);
426
+ return `${indent}${stmt.label.name}:\n${body}`;
427
+ }
428
+ default: {
429
+ let text = stripTrailingSemicolons(source.slice(stmt.start, stmt.end).trimEnd());
430
+ if (text === '')
431
+ return '';
432
+ return indent + text + (text.endsWith('}') || isLast ? '' : ';');
433
+ }
434
+ }
435
+ }
436
+ function tsxEmitBody(source, stmts, indent) {
437
+ const lines = [];
438
+ let i = 0;
439
+ while (i < stmts.length) {
440
+ const stmt = stmts[i];
441
+ const isLast = i === stmts.length - 1;
442
+ if (stmt.type === 'ForOfStatement' &&
443
+ stmts[i + 1] && stmts[i + 1].type === 'VeskBlock' && stmts[i + 1].tag === 'empty') {
444
+ const empty = stmts[i + 1];
445
+ lines.push(tsxEmitForOf(source, stmt, indent));
446
+ const inner = empty.body ?? [];
447
+ if (inner.length > 0) {
448
+ const emptyText = tsxEmitBody(source, inner, indent + ' ');
449
+ const arrExpr = source.slice(stmt.right.start, stmt.right.end);
450
+ lines.push(`${indent}${arrExpr}.length === 0 && (() => {`);
451
+ lines.push(emptyText);
452
+ lines.push(`${indent}})();`);
453
+ }
454
+ i += 2;
455
+ continue;
456
+ }
457
+ const text = tsxEmitStatement(source, stmt, indent, isLast);
458
+ if (text !== '')
459
+ lines.push(text);
460
+ i++;
461
+ }
462
+ return lines.join('\n');
463
+ }
464
+ function isStatementMode(node) {
465
+ const stmts = node.body?.body ?? [];
466
+ return !(stmts.length === 1 && stmts[0].type === 'ReturnStatement');
467
+ }
468
+ /** Replaces the first whole-word occurrence of `word` in `text`. */
469
+ function replaceFirstKeyword(text, word, replacement) {
470
+ let i = 0;
471
+ while (i < text.length) {
472
+ if (isIdentStart(text[i])) {
473
+ let j = i + 1;
474
+ while (j < text.length && isIdentChar(text[j]))
475
+ j++;
476
+ if (text.slice(i, j) === word)
477
+ return text.slice(0, i) + replacement + text.slice(j);
478
+ i = j;
479
+ continue;
480
+ }
481
+ i++;
482
+ }
483
+ return text;
484
+ }
485
+ /**
486
+ * Removes a trailing `client` keyword from a component header (the header is
487
+ * the text from the declaration start up to the body `{`), so `component Foo
488
+ * client {` becomes `component Foo {`. Returns the text with the keyword
489
+ * replaced by a single space.
490
+ */
491
+ function stripTrailingClient(text) {
492
+ let end = text.length;
493
+ while (end > 0 && isWhitespaceChar(text[end - 1]))
494
+ end--;
495
+ let wordStart = end;
496
+ while (wordStart > 0 && isIdentChar(text[wordStart - 1]))
497
+ wordStart--;
498
+ if (text.slice(wordStart, end) === 'client') {
499
+ return text.slice(0, wordStart).trimEnd() + ' ';
500
+ }
501
+ return text;
502
+ }
503
+ export function vskToTsx(source) {
504
+ let ast;
505
+ try {
506
+ ast = parse(source);
507
+ }
508
+ catch {
509
+ return source;
510
+ }
511
+ const clean = blankForClauses(source, ast);
512
+ const edits = [];
513
+ for (const { node, start } of getComponents(ast)) {
514
+ const body = node.body;
515
+ if (!body || body.start == null || body.end == null)
516
+ continue;
517
+ let transformed = stripTrailingClient(replaceFirstKeyword(clean.slice(start, body.start), 'component', 'function'));
518
+ if (!transformed.includes('(')) {
519
+ transformed = transformed.trimEnd() + '() ';
520
+ }
521
+ const statementMode = isStatementMode(node);
522
+ let wrapped;
523
+ if (statementMode) {
524
+ const bodyText = rewriteTrackDecls(tsxEmitBody(clean, body.body ?? [], ''));
525
+ wrapped = '{ ' + bodyText.trim() + ' }';
526
+ }
527
+ else {
528
+ const raw = clean.slice(body.start, body.end);
529
+ const inner = raw.trim();
530
+ const hasBraces = inner.startsWith('{') && inner.endsWith('}');
531
+ const core = hasBraces ? inner.slice(1, -1) : inner;
532
+ const bodyText = rewriteTrackDecls(stripStyleBlocks(core));
533
+ wrapped = hasBraces ? '{ ' + bodyText.trim() + ' }' : bodyText;
534
+ }
535
+ edits.push({ start, end: body.end, text: transformed + wrapped });
536
+ }
537
+ const out = applyEdits(source, edits);
538
+ const isModule = (ast.body || []).some((s) => s.type === 'ImportDeclaration' ||
539
+ s.type === 'ExportNamedDeclaration' ||
540
+ s.type === 'ExportDefaultDeclaration' ||
541
+ s.type === 'ExportAllDeclaration');
542
+ if (isModule && containsIdentifier(out, 'Head') && !isIdentifierImported(out, 'Head')) {
543
+ return `declare const Head: (props: { children?: unknown }) => unknown;\n${out}`;
544
+ }
545
+ return out;
546
+ }
547
+ function isTypeDecl(stmt) {
548
+ return (stmt.type === 'TSInterfaceDeclaration' ||
549
+ stmt.type === 'TSTypeAliasDeclaration' ||
550
+ (stmt.type === 'ExportNamedDeclaration' &&
551
+ (stmt.declaration?.type === 'TSInterfaceDeclaration' || stmt.declaration?.type === 'TSTypeAliasDeclaration')));
552
+ }
553
+ export function generateVskDts(source) {
554
+ let ast;
555
+ try {
556
+ ast = parse(source);
557
+ }
558
+ catch {
559
+ return '';
560
+ }
561
+ const lines = ['/* generated by vesk — do not edit */', ''];
562
+ const typeDeclNames = new Set();
563
+ for (const stmt of ast.body || []) {
564
+ if (isTypeDecl(stmt)) {
565
+ const name = stmt.id?.name || stmt.declaration?.id?.name;
566
+ if (name)
567
+ typeDeclNames.add(name);
568
+ lines.push(source.slice(stmt.start, stmt.end), '');
569
+ }
570
+ }
571
+ for (const stmt of ast.body || []) {
572
+ if (stmt.type === 'ImportDeclaration') {
573
+ const src = stmt.source?.value;
574
+ if (typeof src === 'string' && !src.endsWith('.css')) {
575
+ lines.push(source.slice(stmt.start, stmt.end), '');
576
+ }
577
+ }
578
+ }
579
+ for (const { node, defaultExport } of getComponents(ast)) {
580
+ const name = node.id?.name;
581
+ if (!name)
582
+ continue;
583
+ const propsType = getPropsType(node.params, source);
584
+ const typeName = `${name}Props`;
585
+ const hasType = propsType !== null && propsType !== undefined;
586
+ const collision = typeDeclNames.has(typeName);
587
+ const signature = hasType
588
+ ? (collision ? propsType : typeName) + ' & { children?: unknown }'
589
+ : 'any';
590
+ if (hasType && !collision) {
591
+ lines.push(`export type ${typeName} = ${propsType};`, '');
592
+ }
593
+ else if (!hasType && !collision) {
594
+ lines.push(`export type ${typeName} = any;`, '');
595
+ }
596
+ if (defaultExport) {
597
+ lines.push(`export default function ${name}(props: ${signature}): unknown;`, '');
598
+ }
599
+ else {
600
+ lines.push(`export declare function ${name}(props: ${signature}): unknown;`, '');
601
+ }
602
+ }
603
+ return lines.join('\n');
604
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@vesk/compiler",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "description": "Vesk compiler — lexer, parser, semantic analysis, IR, codegen",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./src/*": "./dist/*.js",
14
+ "./package.json": "./package.json"
15
+ },
16
+ "scripts": {
17
+ "typecheck": "tsc --noEmit",
18
+ "build": "tsc -p tsconfig.build.json && node -e \"require('node:fs').cpSync('src/acorn-ts-plugin', 'dist/acorn-ts-plugin', { recursive: true })\"",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "dependencies": {
22
+ "@sveltejs/acorn-typescript": "^1.0.0",
23
+ "acorn": "^8.15.0",
24
+ "esrap": "^2.3.0",
25
+ "is-reference": "^3.0.0",
26
+ "source-map": "^0.7.4",
27
+ "zimmerframe": "^1.1.0"
28
+ },
29
+ "devDependencies": {
30
+ "typescript": "^5.8.0"
31
+ },
32
+ "license": "MIT",
33
+ "author": "emeraldlinks",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/emeraldlinks/veskTs.git"
37
+ },
38
+ "homepage": "https://github.com/emeraldlinks/veskTs#readme",
39
+ "bugs": {
40
+ "url": "https://github.com/emeraldlinks/veskTs/issues"
41
+ },
42
+ "engines": {
43
+ "node": ">=20.0.0"
44
+ },
45
+ "files": [
46
+ "dist",
47
+ "README.md"
48
+ ],
49
+ "publishConfig": {
50
+ "access": "public"
51
+ }
52
+ }