@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,413 @@
1
+ import * as acorn from 'acorn';
2
+ function isWsChar(code) {
3
+ return code === 32 || code === 9 || code === 10 || code === 13 || code === 12;
4
+ }
5
+ function isStyleBoundary(code) {
6
+ return isWsChar(code) || code === 62 || code === 47 || code === 123;
7
+ }
8
+ function matchKeywordSequence(input, words) {
9
+ let p = 0;
10
+ while (p < input.length && isWsChar(input.charCodeAt(p)))
11
+ p++;
12
+ if (!input.startsWith(words[0], p))
13
+ return false;
14
+ p += words[0].length;
15
+ for (let i = 1; i < words.length; i++) {
16
+ const wsStart = p;
17
+ while (p < input.length && isWsChar(input.charCodeAt(p)))
18
+ p++;
19
+ if (p === wsStart)
20
+ return false;
21
+ if (!input.startsWith(words[i], p))
22
+ return false;
23
+ p += words[i].length;
24
+ }
25
+ if (p >= input.length)
26
+ return true;
27
+ const c = input.charCodeAt(p);
28
+ return isWsChar(c) || c === 123;
29
+ }
30
+ export function VeskParserPlugin(config = {}) {
31
+ return (Parser) => {
32
+ const tt = Parser.tokTypes || acorn.tokTypes;
33
+ const tstt = Parser.acornTypeScript?.tokTypes;
34
+ class VeskParser extends Parser {
35
+ #componentDepth = 0;
36
+ #closeTagName = null;
37
+ #jsxStartsStatement = false;
38
+ constructor(options, input) {
39
+ super(options, input);
40
+ }
41
+ #isBlockContext() {
42
+ const ctx = this.curContext();
43
+ return ctx && (ctx.token === '{' || ctx.token === 'function');
44
+ }
45
+ readToken(code) {
46
+ if (this.#componentDepth > 0 && code === 60 && this.#isBlockContext()) {
47
+ const next = this.input.charCodeAt(this.pos + 1);
48
+ if (next === 47 || (next >= 65 && next <= 90) || (next >= 97 && next <= 122)) {
49
+ const startsNewStatement = this.hasPrecedingLineBreak();
50
+ const inType = this.inType;
51
+ const forceJsx = () => {
52
+ const savedExprAllowed = this.exprAllowed;
53
+ this.exprAllowed = true;
54
+ const result = super.readToken(code);
55
+ this.exprAllowed = savedExprAllowed;
56
+ return result;
57
+ };
58
+ if (inType) {
59
+ if (startsNewStatement) {
60
+ this.#jsxStartsStatement = true;
61
+ ++this.pos;
62
+ return this.finishToken(tstt.jsxTagStart);
63
+ }
64
+ }
65
+ else {
66
+ const prev = this.type;
67
+ const canEndExpr = prev === tt.name || prev === tt.num || prev === tt.string || prev === tt.regexp ||
68
+ prev === tt.bracketR || prev === tt.backQuote || prev === tt.template ||
69
+ prev === tt._this || prev === tt._super || prev === tt._true || prev === tt._false ||
70
+ prev === tt._null || prev === tt.jsxTagEnd;
71
+ if (!canEndExpr || startsNewStatement) {
72
+ this.#jsxStartsStatement = true;
73
+ return forceJsx();
74
+ }
75
+ }
76
+ }
77
+ }
78
+ this.#jsxStartsStatement = false;
79
+ return super.readToken(code);
80
+ }
81
+ isLet(context) {
82
+ if (!this.isContextual('let'))
83
+ return false;
84
+ let p = this.pos;
85
+ while (p < this.input.length && isWsChar(this.input.charCodeAt(p)))
86
+ p++;
87
+ const nextCh = this.input.charCodeAt(p);
88
+ if (nextCh === 38) {
89
+ const afterAmp = this.input.charCodeAt(p + 1);
90
+ if (afterAmp === 123 || afterAmp === 91)
91
+ return true;
92
+ }
93
+ return super.isLet(context);
94
+ }
95
+ parseBindingAtom() {
96
+ if (this.type === tt.bitwiseAND) {
97
+ const charAfterAmp = this.input.charCodeAt(this.end);
98
+ if (charAfterAmp === 123 || charAfterAmp === 91) {
99
+ this.next();
100
+ const pattern = super.parseBindingAtom();
101
+ pattern.lazy = true;
102
+ return pattern;
103
+ }
104
+ }
105
+ return super.parseBindingAtom();
106
+ }
107
+ parseExpression(noIn, refDestructuringErrors) {
108
+ const expr = super.parseExpression(noIn, refDestructuringErrors);
109
+ if (tstt && this.type === tstt.jsxTagStart && !this.#jsxStartsStatement) {
110
+ this.raise(this.start, "Adjacent JSX elements must be wrapped in an enclosing tag. Wrap them in a fragment: <><Comp1 /><Comp2 /></> or a single parent element.");
111
+ }
112
+ return expr;
113
+ }
114
+ parseStatement(context, ...args) {
115
+ if (this.type === tt.name && this.value === 'async') {
116
+ const afterAsync = this.input.slice(this.end).trimStart();
117
+ if (matchKeywordSequence(afterAsync, ['component'])) {
118
+ this.next();
119
+ const node = this.parseComponentDeclaration(true);
120
+ return node;
121
+ }
122
+ }
123
+ if (this.type === tt.name && this.value === 'component') {
124
+ return this.parseComponentDeclaration(false);
125
+ }
126
+ if (this.type === tt._export) {
127
+ const rest = this.input.slice(this.pos).trimStart();
128
+ if (matchKeywordSequence(rest, ['default', 'async', 'component'])) {
129
+ this.next();
130
+ this.next();
131
+ this.next();
132
+ const node = this.startNode();
133
+ node.declaration = this.parseComponentDeclaration(true);
134
+ node.default = true;
135
+ return this.finishNode(node, 'ExportDefaultDeclaration');
136
+ }
137
+ if (matchKeywordSequence(rest, ['async', 'component'])) {
138
+ this.next();
139
+ this.next();
140
+ const node = this.startNode();
141
+ node.declaration = this.parseComponentDeclaration(true);
142
+ return this.finishNode(node, 'ExportNamedDeclaration');
143
+ }
144
+ if (matchKeywordSequence(rest, ['default', 'component'])) {
145
+ const node = this.startNode();
146
+ this.next();
147
+ this.next();
148
+ node.declaration = this.parseComponentDeclaration(false);
149
+ node.default = true;
150
+ return this.finishNode(node, 'ExportDefaultDeclaration');
151
+ }
152
+ if (matchKeywordSequence(rest, ['component'])) {
153
+ const node = this.startNode();
154
+ this.next();
155
+ node.declaration = this.parseComponentDeclaration(false);
156
+ return this.finishNode(node, 'ExportNamedDeclaration');
157
+ }
158
+ }
159
+ if (tstt && this.type === tstt.jsxTagStart && this.#componentDepth > 0) {
160
+ const node = this.jsx_parseElement();
161
+ this.semicolon();
162
+ return node;
163
+ }
164
+ if (this.#componentDepth > 0 && this.type === tt.privateId && (this.value === 'server' || this.value === 'client' || this.value === 'empty')) {
165
+ let p = this.end;
166
+ while (p < this.input.length && isWsChar(this.input.charCodeAt(p)))
167
+ p++;
168
+ if (this.input.charCodeAt(p) === 123) {
169
+ const node = this.startNode();
170
+ node.tag = this.value;
171
+ node.body = [];
172
+ this.next();
173
+ this.expect(tt.braceL);
174
+ this.parseTemplateBody(node.body);
175
+ this.next();
176
+ return this.finishNode(node, 'VeskBlock');
177
+ }
178
+ }
179
+ if (this.#componentDepth > 0 && this.type === tt.name && this.value === 'empty') {
180
+ let p = this.end;
181
+ while (p < this.input.length && isWsChar(this.input.charCodeAt(p)))
182
+ p++;
183
+ if (this.input.charCodeAt(p) === 123) {
184
+ const node = this.startNode();
185
+ node.tag = 'empty';
186
+ this.next();
187
+ node.body = this.parseBlock(false).body;
188
+ return this.finishNode(node, 'VeskBlock');
189
+ }
190
+ }
191
+ return super.parseStatement(context, ...args);
192
+ }
193
+ parseBlock(createNewLexicalScope, node, exitStrict) {
194
+ if (this.#componentDepth > 0) {
195
+ if (createNewLexicalScope === void 0)
196
+ createNewLexicalScope = true;
197
+ if (node === void 0)
198
+ node = this.startNode();
199
+ node.body = [];
200
+ this.expect(tt.braceL);
201
+ if (createNewLexicalScope) {
202
+ this.enterScope(0);
203
+ }
204
+ this.parseTemplateBody(node.body);
205
+ if (exitStrict) {
206
+ this.strict = false;
207
+ }
208
+ this.exprAllowed = true;
209
+ this.next();
210
+ if (createNewLexicalScope) {
211
+ this.exitScope();
212
+ }
213
+ return this.finishNode(node, 'BlockStatement');
214
+ }
215
+ return super.parseBlock(createNewLexicalScope, node, exitStrict);
216
+ }
217
+ parseTemplateBody(body) {
218
+ if (this.type === tt.braceL) {
219
+ const peekChar = this.input.charCodeAt(this.start + 1);
220
+ if (this.#closeTagName !== null && peekChar === 47) {
221
+ let p = this.start + 2;
222
+ const nameStart = p;
223
+ while (p < this.input.length) {
224
+ const c = this.input.charCodeAt(p);
225
+ if ((c >= 97 && c <= 122) || (c >= 65 && c <= 90) || (c >= 48 && c <= 57) || c === 95 || c === 36) {
226
+ p++;
227
+ }
228
+ else
229
+ break;
230
+ }
231
+ const closeTag = this.input.slice(nameStart, p);
232
+ if (closeTag === this.#closeTagName && p < this.input.length && this.input.charCodeAt(p) === 125) {
233
+ return;
234
+ }
235
+ }
236
+ if (peekChar === 35) {
237
+ let p = this.start + 2;
238
+ const nameStart = p;
239
+ while (p < this.input.length) {
240
+ const c = this.input.charCodeAt(p);
241
+ if ((c >= 97 && c <= 122) || (c >= 65 && c <= 90) || (c >= 48 && c <= 57) || c === 95 || c === 36) {
242
+ p++;
243
+ }
244
+ else
245
+ break;
246
+ }
247
+ const tagName = this.input.slice(nameStart, p);
248
+ if (tagName === 'server' || tagName === 'client') {
249
+ if (p < this.input.length && this.input.charCodeAt(p) === 125) {
250
+ const node = this.parseVeskBlock(tagName);
251
+ body.push(node);
252
+ this.parseTemplateBody(body);
253
+ return;
254
+ }
255
+ }
256
+ }
257
+ const node = this.jsx_parseExpressionContainer();
258
+ body.push(node);
259
+ }
260
+ else if (this.type === tt.braceR) {
261
+ return;
262
+ }
263
+ else if (tstt && this.type === tstt.jsxTagStart) {
264
+ const nextChar = this.input.charCodeAt(this.pos);
265
+ if (nextChar === 47) {
266
+ return;
267
+ }
268
+ const node = this.jsx_parseElement();
269
+ body.push(node);
270
+ this.exprAllowed = true;
271
+ }
272
+ else if (tstt && this.type === tstt.jsxText) {
273
+ this.next();
274
+ }
275
+ else {
276
+ const node = this.parseStatement(null);
277
+ body.push(node);
278
+ this.exprAllowed = true;
279
+ }
280
+ this.parseTemplateBody(body);
281
+ }
282
+ parseVeskBlock(tagName) {
283
+ const node = this.startNode();
284
+ node.tag = tagName;
285
+ node.body = [];
286
+ this.pos = this.start;
287
+ const endPos = this.start + 2 + tagName.length + 1;
288
+ if (endPos > this.input.length) {
289
+ this.raise(this.start, `Unclosed ${tagName} block`);
290
+ }
291
+ this.pos++;
292
+ this.pos++;
293
+ this.pos += tagName.length;
294
+ this.pos++;
295
+ this.next();
296
+ const prevCloseTag = this.#closeTagName;
297
+ this.#closeTagName = tagName;
298
+ this.parseTemplateBody(node.body);
299
+ this.#closeTagName = prevCloseTag;
300
+ this.pos = this.start;
301
+ this.pos++;
302
+ this.pos++;
303
+ while (this.pos < this.input.length) {
304
+ const c = this.input.charCodeAt(this.pos);
305
+ if ((c >= 97 && c <= 122) || (c >= 65 && c <= 90) || (c >= 48 && c <= 57) || c === 95 || c === 36) {
306
+ this.pos++;
307
+ }
308
+ else
309
+ break;
310
+ }
311
+ if (this.pos < this.input.length)
312
+ this.pos++;
313
+ this.next();
314
+ return this.finishNode(node, 'VeskBlock');
315
+ }
316
+ parseComponentDeclaration(isAsync = false) {
317
+ const node = this.startNode();
318
+ node.async = isAsync;
319
+ this.next();
320
+ node.id = this.parseIdent();
321
+ if (typeof this.tsTryParseTypeParameters === 'function') {
322
+ const typeParameters = this.tsTryParseTypeParameters(this.tsParseConstModifier);
323
+ if (typeParameters)
324
+ node.typeParameters = typeParameters;
325
+ }
326
+ // Mirror acorn's parseFunction: clear await/yield bookkeeping so a
327
+ // later component declaration doesn't trip checkYieldAwaitInDefaultParams
328
+ // on stale positions from a previous async component body.
329
+ const savedYield = this.yieldPos;
330
+ const savedAwait = this.awaitPos;
331
+ const savedAwaitIdent = this.awaitIdentPos;
332
+ const savedMaybeInArrow = this.maybeInArrowParameters;
333
+ this.yieldPos = 0;
334
+ this.awaitPos = 0;
335
+ this.awaitIdentPos = 0;
336
+ this.maybeInArrowParameters = false;
337
+ this.enterScope(2 | (isAsync ? 4 : 0));
338
+ node.client = (this.type === tt.name || this.type === tt.privateId) && this.value === 'client' ? (this.next(), true) : false;
339
+ if (this.type === tt.parenL) {
340
+ this.parseFunctionParams(node);
341
+ }
342
+ else {
343
+ node.params = [];
344
+ }
345
+ if (!node.client && (this.type === tt.name || this.type === tt.privateId) && this.value === 'client') {
346
+ node.client = true;
347
+ this.next();
348
+ }
349
+ this.#componentDepth++;
350
+ node.body = this.parseBlock(false);
351
+ this.#componentDepth--;
352
+ this.exitScope();
353
+ this.yieldPos = savedYield;
354
+ this.awaitPos = savedAwait;
355
+ this.awaitIdentPos = savedAwaitIdent;
356
+ this.maybeInArrowParameters = savedMaybeInArrow;
357
+ this.finishNode(node, 'ComponentDeclaration');
358
+ return node;
359
+ }
360
+ jsx_parseElementAt(startPos, startLoc) {
361
+ const prefixLen = Math.min(20, this.input.length - startPos);
362
+ const prefix = this.input.slice(startPos, startPos + prefixLen);
363
+ if (prefix.startsWith('<style') && isStyleBoundary(prefix.charCodeAt(6))) {
364
+ return this.parseStyleElement(startPos, startLoc);
365
+ }
366
+ return super.jsx_parseElementAt(startPos, startLoc);
367
+ }
368
+ parseStyleElement(startPos, startLoc) {
369
+ const el = this.startNodeAt(startPos, startLoc);
370
+ const openNode = this.startNodeAt(startPos, startLoc);
371
+ let nodeName = this.jsx_parseElementName();
372
+ if (nodeName)
373
+ openNode.name = nodeName;
374
+ openNode.attributes = [];
375
+ this.lastTokEnd = this.pos;
376
+ openNode.selfClosing = false;
377
+ const openingElement = this.finishNode(openNode, 'JSXOpeningElement');
378
+ if (openingElement.selfClosing) {
379
+ el.openingElement = openingElement;
380
+ el.children = [];
381
+ return this.finishNode(el, 'JSXElement');
382
+ }
383
+ const contentStart = openingElement.end;
384
+ const closeIdx = this.input.indexOf('</style>', contentStart);
385
+ if (closeIdx < 0) {
386
+ this.raise(contentStart, 'Unclosed <style> element: missing </style>');
387
+ }
388
+ const children = [];
389
+ if (closeIdx > contentStart) {
390
+ const raw = this.input.slice(contentStart, closeIdx);
391
+ children.push({ type: 'JSXText', value: raw });
392
+ }
393
+ this.pos = closeIdx;
394
+ this.next();
395
+ this.next();
396
+ this.eat(tt.slash);
397
+ this.lastTokEnd = this.pos;
398
+ const closingElement = this.jsx_parseClosingElementAt(closeIdx, undefined);
399
+ el.openingElement = openingElement;
400
+ el.closingElement = closingElement;
401
+ el.children = children;
402
+ return this.finishNode(el, 'JSXElement');
403
+ }
404
+ checkUnreserved(ref) {
405
+ if (ref.name === 'component') {
406
+ this.raise(ref.start, '`component` is a reserved keyword and cannot be used as an identifier');
407
+ }
408
+ return super.checkUnreserved(ref);
409
+ }
410
+ }
411
+ return VeskParser;
412
+ };
413
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Resolve `import ... from './path.vsk'` statements so helper components can
3
+ * live in arbitrary `.vsk` files and be imported into any page, layout,
4
+ * component or route file.
5
+ */
6
+ export declare function vskImportTarget(importText: string): string | null;
7
+ /**
8
+ * Extracts import statements from a `.vsk` source file. The source is parsed
9
+ * with the Vesk parser so imports inside strings, comments or template
10
+ * literals are never mistaken for real imports, and multi-line import lists
11
+ * are handled correctly. Falls back to a tokenizer scan when the file does
12
+ * not parse (e.g. mid-edit content in tooling).
13
+ */
14
+ export declare function extractImportStatements(source: string): string[];
15
+ export declare function collectVskImportPaths(imports: string[], sourcePath: string): string[];
16
+ export declare function vskImportLines(source: string): string[];
17
+ /**
18
+ * True when an `ImportDeclaration` is type-only: an `import type { ... }`
19
+ * statement, or a value import whose specifiers are all `type` specifiers.
20
+ * Type imports carry no runtime value and must never reach emitted JS.
21
+ */
22
+ export declare function isTypeOnlyImport(stmt: any): boolean;
23
+ /**
24
+ * Returns the import source with `type` specifiers removed, or `null` when the
25
+ * whole statement is type-only (nothing left to import at runtime). The
26
+ * original source is returned untouched when it cannot be parsed or reprinted,
27
+ * so value imports are never dropped or mangled.
28
+ */
29
+ export declare function stripTypeImport(importSrc: string): string | null;
30
+ //# sourceMappingURL=vsk-imports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vsk-imports.d.ts","sourceRoot":"","sources":["../src/vsk-imports.ts"],"names":[],"mappings":"AAOA;;;;GAIG;AAEH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQjE;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAahE;AA0BD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAUrF;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAEvD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAKnD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAoBhE"}
@@ -0,0 +1,141 @@
1
+ import { dirname, resolve } from 'node:path';
2
+ import { existsSync } from 'node:fs';
3
+ import { print } from 'esrap';
4
+ import ts from 'esrap/languages/ts';
5
+ import { parse } from '@vesk/compiler/src/parser';
6
+ import { importModuleTarget, tokenizeCode } from '@vesk/compiler/src/tokens';
7
+ /**
8
+ * Resolve `import ... from './path.vsk'` statements so helper components can
9
+ * live in arbitrary `.vsk` files and be imported into any page, layout,
10
+ * component or route file.
11
+ */
12
+ export function vskImportTarget(importText) {
13
+ const spec = importModuleTarget(importText);
14
+ if (spec === null || !spec.endsWith('.vsk'))
15
+ return null;
16
+ const tokens = tokenizeCode(importText);
17
+ if (tokens === null)
18
+ return null;
19
+ const hasFrom = tokens.some((t) => t.label === 'name' && t.value === 'from');
20
+ if (!hasFrom)
21
+ return null;
22
+ return spec;
23
+ }
24
+ /**
25
+ * Extracts import statements from a `.vsk` source file. The source is parsed
26
+ * with the Vesk parser so imports inside strings, comments or template
27
+ * literals are never mistaken for real imports, and multi-line import lists
28
+ * are handled correctly. Falls back to a tokenizer scan when the file does
29
+ * not parse (e.g. mid-edit content in tooling).
30
+ */
31
+ export function extractImportStatements(source) {
32
+ try {
33
+ const ast = parse(source, { filename: 'imports.vsk' });
34
+ const out = [];
35
+ for (const stmt of ast.body) {
36
+ if (stmt.type === 'ImportDeclaration') {
37
+ out.push(source.slice(stmt.start, stmt.end));
38
+ }
39
+ }
40
+ return out;
41
+ }
42
+ catch {
43
+ return tokenExtractImportStatements(source);
44
+ }
45
+ }
46
+ function tokenExtractImportStatements(source) {
47
+ const tokens = tokenizeCode(source);
48
+ if (tokens === null)
49
+ return [];
50
+ const out = [];
51
+ for (let i = 0; i < tokens.length; i++) {
52
+ if (tokens[i].label !== 'import')
53
+ continue;
54
+ const start = tokens[i].start;
55
+ let lastStringEnd = -1;
56
+ let end = -1;
57
+ let j = i + 1;
58
+ while (j < tokens.length) {
59
+ if (tokens[j].label === ';') {
60
+ end = tokens[j].end;
61
+ break;
62
+ }
63
+ if (tokens[j].label === 'import')
64
+ break;
65
+ if (tokens[j].label === 'string')
66
+ lastStringEnd = tokens[j].end;
67
+ j++;
68
+ }
69
+ if (end === -1)
70
+ end = lastStringEnd !== -1 ? lastStringEnd : tokens[i].end;
71
+ const stmt = source.slice(start, end).trim();
72
+ if (stmt)
73
+ out.push(stmt);
74
+ if (tokens[j] && tokens[j].label === ';')
75
+ i = j;
76
+ }
77
+ return out;
78
+ }
79
+ export function collectVskImportPaths(imports, sourcePath) {
80
+ const out = [];
81
+ for (const imp of imports) {
82
+ if (stripTypeImport(imp) === null)
83
+ continue;
84
+ const target = vskImportTarget(imp);
85
+ if (!target || !target.startsWith('.'))
86
+ continue;
87
+ const full = resolve(dirname(sourcePath), target);
88
+ if (existsSync(full))
89
+ out.push(full);
90
+ }
91
+ return out;
92
+ }
93
+ export function vskImportLines(source) {
94
+ return extractImportStatements(source).filter((imp) => vskImportTarget(imp) !== null && stripTypeImport(imp) !== null);
95
+ }
96
+ /**
97
+ * True when an `ImportDeclaration` is type-only: an `import type { ... }`
98
+ * statement, or a value import whose specifiers are all `type` specifiers.
99
+ * Type imports carry no runtime value and must never reach emitted JS.
100
+ */
101
+ export function isTypeOnlyImport(stmt) {
102
+ if (!stmt || stmt.type !== 'ImportDeclaration')
103
+ return false;
104
+ if (stmt.importKind === 'type')
105
+ return true;
106
+ const specs = stmt.specifiers || [];
107
+ return specs.length > 0 && specs.every((s) => s.importKind === 'type');
108
+ }
109
+ /**
110
+ * Returns the import source with `type` specifiers removed, or `null` when the
111
+ * whole statement is type-only (nothing left to import at runtime). The
112
+ * original source is returned untouched when it cannot be parsed or reprinted,
113
+ * so value imports are never dropped or mangled.
114
+ */
115
+ export function stripTypeImport(importSrc) {
116
+ let ast;
117
+ try {
118
+ ast = parse(importSrc, { filename: 'import.mjs' });
119
+ }
120
+ catch {
121
+ return importSrc;
122
+ }
123
+ const stmt = (ast.body || []).find((n) => n.type === 'ImportDeclaration');
124
+ if (!stmt)
125
+ return importSrc;
126
+ if (isTypeOnlyImport(stmt))
127
+ return null;
128
+ const specs = stmt.specifiers || [];
129
+ const kept = specs.filter((s) => s.importKind !== 'type');
130
+ if (kept.length === 0)
131
+ return null;
132
+ if (kept.length === specs.length)
133
+ return importSrc;
134
+ try {
135
+ const rewritten = { ...stmt, specifiers: kept };
136
+ return print({ type: 'Program', body: [rewritten], sourceType: 'module' }, ts()).code.trim();
137
+ }
138
+ catch {
139
+ return importSrc;
140
+ }
141
+ }
@@ -0,0 +1,3 @@
1
+ export declare function vskToTsx(source: string): string;
2
+ export declare function generateVskDts(source: string): string;
3
+ //# sourceMappingURL=vsk-tsx.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vsk-tsx.d.ts","sourceRoot":"","sources":["../src/vsk-tsx.ts"],"names":[],"mappings":"AAmfA,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA8C/C;AAWD,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAmDrD"}