@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
package/dist/router.js
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import { readdirSync, statSync, existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { join, relative, basename } from 'path';
|
|
3
|
+
import { parse } from '@vesk/compiler/src/parser';
|
|
4
|
+
import { tokenizeCode } from '@vesk/compiler/src/tokens';
|
|
5
|
+
import { skipWhitespace, findBalancedEnd } from '@vesk/compiler/src/scan';
|
|
6
|
+
import { hasTsSyntax, stripCodeTypes } from '@vesk/compiler/src/strip-ts';
|
|
7
|
+
/**
|
|
8
|
+
* Extracts `export (async) function middleware(params) { body }` from
|
|
9
|
+
* middleware source text. Parses with acorn (via the Vesk parser) so
|
|
10
|
+
* default params, nested parens and strings in the parameter list are
|
|
11
|
+
* handled correctly; falls back to the old regex + brace balance when the
|
|
12
|
+
* source does not parse.
|
|
13
|
+
*/
|
|
14
|
+
export function extractMiddlewareParts(src) {
|
|
15
|
+
try {
|
|
16
|
+
let ast = parse(src, { filename: 'middleware.ts' });
|
|
17
|
+
let target = findMiddlewareFn(ast);
|
|
18
|
+
if (!target)
|
|
19
|
+
return null;
|
|
20
|
+
if (hasTsSyntax(ast)) {
|
|
21
|
+
const stripped = stripCodeTypes(src);
|
|
22
|
+
if (stripped !== src) {
|
|
23
|
+
src = stripped;
|
|
24
|
+
ast = parse(src, { filename: 'middleware.ts' });
|
|
25
|
+
target = findMiddlewareFn(ast);
|
|
26
|
+
if (!target)
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const params = target.params.length
|
|
31
|
+
? src.slice(target.params[0].start, target.params[target.params.length - 1].end)
|
|
32
|
+
: '';
|
|
33
|
+
const body = src.slice(target.body.start + 1, target.body.end - 1);
|
|
34
|
+
return { params, body: body.trim() };
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return fallbackExtractMiddleware(src);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function findMiddlewareFn(ast) {
|
|
41
|
+
for (const stmt of ast.body) {
|
|
42
|
+
const target = stmt.type === 'ExportNamedDeclaration' ? stmt.declaration : stmt;
|
|
43
|
+
if (!target || target.type !== 'FunctionDeclaration')
|
|
44
|
+
continue;
|
|
45
|
+
if (!(target.id && target.id.name === 'middleware'))
|
|
46
|
+
continue;
|
|
47
|
+
return target;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Token-based fallback for `export (async) function middleware(params) { body }`
|
|
53
|
+
* used when the source does not parse. Identifies the `middleware` function by
|
|
54
|
+
* its token neighbours, then extracts the parameter list and body with balanced
|
|
55
|
+
* delimiter scans (so nested parens/braces and strings stay intact).
|
|
56
|
+
*/
|
|
57
|
+
function fallbackExtractMiddleware(src) {
|
|
58
|
+
const tokens = tokenizeCode(src);
|
|
59
|
+
if (tokens === null)
|
|
60
|
+
return null;
|
|
61
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
62
|
+
const t = tokens[i];
|
|
63
|
+
if (t.label !== 'name' || t.value !== 'middleware')
|
|
64
|
+
continue;
|
|
65
|
+
const fnTok = tokens[i - 1];
|
|
66
|
+
if (!fnTok || fnTok.label !== 'name' || fnTok.value !== 'function')
|
|
67
|
+
continue;
|
|
68
|
+
let prevIdx = i - 2;
|
|
69
|
+
if (tokens[prevIdx] && tokens[prevIdx].label === 'name' && tokens[prevIdx].value === 'async')
|
|
70
|
+
prevIdx--;
|
|
71
|
+
const exportTok = tokens[prevIdx];
|
|
72
|
+
if (!exportTok || exportTok.label !== 'export')
|
|
73
|
+
continue;
|
|
74
|
+
let k = i + 1;
|
|
75
|
+
while (k < tokens.length && tokens[k].label !== '(')
|
|
76
|
+
k++;
|
|
77
|
+
if (k >= tokens.length)
|
|
78
|
+
continue;
|
|
79
|
+
const paramsEnd = findBalancedEnd(src, tokens[k].start);
|
|
80
|
+
const params = src.slice(tokens[k].start + 1, paramsEnd);
|
|
81
|
+
let b = skipWhitespace(src, paramsEnd + 1);
|
|
82
|
+
if (src[b] !== '{')
|
|
83
|
+
continue;
|
|
84
|
+
let depth = 1;
|
|
85
|
+
let m = b + 1;
|
|
86
|
+
while (m < src.length && depth > 0) {
|
|
87
|
+
if (src[m] === '{')
|
|
88
|
+
depth++;
|
|
89
|
+
else if (src[m] === '}')
|
|
90
|
+
depth--;
|
|
91
|
+
m++;
|
|
92
|
+
}
|
|
93
|
+
const body = src.slice(b + 1, m - 1);
|
|
94
|
+
return { params, body: body.trim() };
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
export function extractMiddleware(sourcePath) {
|
|
99
|
+
try {
|
|
100
|
+
if (!existsSync(sourcePath))
|
|
101
|
+
return null;
|
|
102
|
+
const src = readFileSync(sourcePath, 'utf-8');
|
|
103
|
+
const parts = extractMiddlewareParts(src);
|
|
104
|
+
if (!parts)
|
|
105
|
+
return null;
|
|
106
|
+
return `async function middleware(${parts.params}) {\n${parts.body}\n}`;
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export function scanRoutes(appDir, options = {}) {
|
|
113
|
+
if (!existsSync(appDir)) {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
return scanDirectory(appDir, appDir, '/', options);
|
|
117
|
+
}
|
|
118
|
+
function capitalize(s) {
|
|
119
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
120
|
+
}
|
|
121
|
+
export function scanComponents(componentsDir) {
|
|
122
|
+
const map = new Map();
|
|
123
|
+
if (!existsSync(componentsDir))
|
|
124
|
+
return map;
|
|
125
|
+
function walk(dir, prefix) {
|
|
126
|
+
let entries;
|
|
127
|
+
try {
|
|
128
|
+
entries = readdirSync(dir);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
for (const entry of entries) {
|
|
134
|
+
const full = join(dir, entry);
|
|
135
|
+
let stat;
|
|
136
|
+
try {
|
|
137
|
+
stat = statSync(full);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (stat.isDirectory()) {
|
|
143
|
+
if (!entry.startsWith('_')) {
|
|
144
|
+
walk(full, prefix ? prefix + capitalize(entry) : capitalize(entry));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else if (entry.endsWith('.vsk')) {
|
|
148
|
+
const name = prefix
|
|
149
|
+
? prefix + capitalize(entry.slice(0, -4))
|
|
150
|
+
: entry.slice(0, -4);
|
|
151
|
+
if (!map.has(name)) {
|
|
152
|
+
map.set(name, full);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
walk(componentsDir, '');
|
|
158
|
+
return map;
|
|
159
|
+
}
|
|
160
|
+
function scanDirectory(rootDir, dir, parentPath, options) {
|
|
161
|
+
const nodes = [];
|
|
162
|
+
let entries;
|
|
163
|
+
try {
|
|
164
|
+
entries = readdirSync(dir);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return nodes;
|
|
168
|
+
}
|
|
169
|
+
entries.sort((a, b) => {
|
|
170
|
+
const aIsSpecial = a === 'page.vsk' || a === 'layout.vsk' || a === 'not-found.vsk';
|
|
171
|
+
const bIsSpecial = b === 'page.vsk' || b === 'layout.vsk' || b === 'not-found.vsk';
|
|
172
|
+
if (aIsSpecial && !bIsSpecial)
|
|
173
|
+
return -1;
|
|
174
|
+
if (!aIsSpecial && bIsSpecial)
|
|
175
|
+
return 1;
|
|
176
|
+
return a.localeCompare(b);
|
|
177
|
+
});
|
|
178
|
+
let hasLayout = false;
|
|
179
|
+
let hasPage = false;
|
|
180
|
+
let hasLoading = false;
|
|
181
|
+
let hasError = false;
|
|
182
|
+
let hasNotFound = false;
|
|
183
|
+
let hasMiddleware = false;
|
|
184
|
+
for (const entry of entries) {
|
|
185
|
+
if (entry === 'layout.vsk') {
|
|
186
|
+
hasLayout = true;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (entry === 'page.vsk') {
|
|
190
|
+
hasPage = true;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (entry === 'loading.vsk') {
|
|
194
|
+
hasLoading = true;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (entry === 'error.vsk') {
|
|
198
|
+
hasError = true;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (entry === 'not-found.vsk') {
|
|
202
|
+
hasNotFound = true;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (entry === 'middleware.ts') {
|
|
206
|
+
hasMiddleware = true;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
let segName = basename(dir);
|
|
211
|
+
let isGroup = segName.startsWith('(') && segName.endsWith(')');
|
|
212
|
+
let isDynamic = segName.startsWith('[') && segName.endsWith(']') && !segName.startsWith('[...');
|
|
213
|
+
let isCatchAll = segName.startsWith('[...') && segName.endsWith(']');
|
|
214
|
+
let isPrivate = segName.startsWith('_');
|
|
215
|
+
if (isPrivate && dir !== rootDir)
|
|
216
|
+
return nodes;
|
|
217
|
+
let seg = '';
|
|
218
|
+
if (dir === rootDir) {
|
|
219
|
+
seg = '';
|
|
220
|
+
}
|
|
221
|
+
else if (isGroup) {
|
|
222
|
+
seg = '';
|
|
223
|
+
}
|
|
224
|
+
else if (isDynamic) {
|
|
225
|
+
seg = ':' + segName.slice(1, -1);
|
|
226
|
+
}
|
|
227
|
+
else if (isCatchAll) {
|
|
228
|
+
seg = ':' + segName.slice(4, -1);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
seg = segName;
|
|
232
|
+
}
|
|
233
|
+
const fullPath = seg
|
|
234
|
+
? (parentPath === '/' ? '/' : parentPath + '/') + seg
|
|
235
|
+
: (parentPath || '/');
|
|
236
|
+
const node = {
|
|
237
|
+
path: seg,
|
|
238
|
+
fullPath: fullPath.replace(/\/+/g, '/') || '/',
|
|
239
|
+
isGroup,
|
|
240
|
+
isDynamic,
|
|
241
|
+
isCatchAll,
|
|
242
|
+
page: hasPage ? extractComponentName(dir, 'page', rootDir) : null,
|
|
243
|
+
layout: hasLayout ? extractComponentName(dir, 'layout', rootDir) : null,
|
|
244
|
+
loading: hasLoading ? extractComponentName(dir, 'loading', rootDir) : null,
|
|
245
|
+
error: hasError ? extractComponentName(dir, 'error', rootDir) : null,
|
|
246
|
+
notFound: hasNotFound ? extractComponentName(dir, 'not-found', rootDir) : null,
|
|
247
|
+
hasMiddleware,
|
|
248
|
+
children: [],
|
|
249
|
+
sourceDir: dir,
|
|
250
|
+
segmentCount: isGroup || dir === rootDir ? 0 : 1,
|
|
251
|
+
};
|
|
252
|
+
for (const entry of entries) {
|
|
253
|
+
const entryPath = join(dir, entry);
|
|
254
|
+
let entryStat;
|
|
255
|
+
try {
|
|
256
|
+
entryStat = statSync(entryPath);
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (entryStat.isDirectory()) {
|
|
262
|
+
const childNodes = scanDirectory(rootDir, entryPath, fullPath, options);
|
|
263
|
+
node.children.push(...childNodes);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (node.page || node.layout || node.children.length > 0) {
|
|
267
|
+
nodes.push(node);
|
|
268
|
+
}
|
|
269
|
+
return nodes;
|
|
270
|
+
}
|
|
271
|
+
function extractComponentName(dir, type, rootDir) {
|
|
272
|
+
const rel = relative(rootDir, dir);
|
|
273
|
+
const parts = rel.split('/').filter(Boolean);
|
|
274
|
+
const clean = parts.map(p => {
|
|
275
|
+
let out = '';
|
|
276
|
+
for (const ch of p) {
|
|
277
|
+
if (ch === '[' || ch === ']' || ch === '(' || ch === ')' || ch === '.')
|
|
278
|
+
continue;
|
|
279
|
+
out += ch;
|
|
280
|
+
}
|
|
281
|
+
return out;
|
|
282
|
+
});
|
|
283
|
+
const suffix = clean.length > 0 ? clean.join('_') : 'index';
|
|
284
|
+
const capitalized = suffix.charAt(0).toUpperCase() + suffix.slice(1);
|
|
285
|
+
if (type === 'page')
|
|
286
|
+
return 'Page_' + capitalized;
|
|
287
|
+
if (type === 'layout')
|
|
288
|
+
return 'Layout_' + capitalized;
|
|
289
|
+
if (type === 'loading')
|
|
290
|
+
return 'Loading_' + capitalized;
|
|
291
|
+
if (type === 'error')
|
|
292
|
+
return 'Error_' + capitalized;
|
|
293
|
+
if (type === 'not-found')
|
|
294
|
+
return 'NotFound_' + capitalized;
|
|
295
|
+
return type + '_' + capitalized;
|
|
296
|
+
}
|
|
297
|
+
export function collectSources(tree) {
|
|
298
|
+
const map = new Map();
|
|
299
|
+
function walk(nodes) {
|
|
300
|
+
for (const node of nodes) {
|
|
301
|
+
if (node.page)
|
|
302
|
+
map.set(node.page, join(node.sourceDir, 'page.vsk'));
|
|
303
|
+
if (node.layout)
|
|
304
|
+
map.set(node.layout, join(node.sourceDir, 'layout.vsk'));
|
|
305
|
+
if (node.loading)
|
|
306
|
+
map.set(node.loading, join(node.sourceDir, 'loading.vsk'));
|
|
307
|
+
if (node.error)
|
|
308
|
+
map.set(node.error, join(node.sourceDir, 'error.vsk'));
|
|
309
|
+
if (node.notFound)
|
|
310
|
+
map.set(node.notFound, join(node.sourceDir, 'not-found.vsk'));
|
|
311
|
+
walk(node.children);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
walk(tree);
|
|
315
|
+
return map;
|
|
316
|
+
}
|
|
317
|
+
export function generateRouteManifest(tree, options = {}) {
|
|
318
|
+
const prefix = options.importPrefix || './';
|
|
319
|
+
function genNode(node, _isRoot = false) {
|
|
320
|
+
const parts = [];
|
|
321
|
+
if (node.page)
|
|
322
|
+
parts.push(`page: ${node.page}`);
|
|
323
|
+
if (node.layout)
|
|
324
|
+
parts.push(`layout: ${node.layout}`);
|
|
325
|
+
if (node.loading)
|
|
326
|
+
parts.push(`loading: ${node.loading}`);
|
|
327
|
+
if (node.error)
|
|
328
|
+
parts.push(`error: ${node.error}`);
|
|
329
|
+
if (node.notFound)
|
|
330
|
+
parts.push(`notFound: ${node.notFound}`);
|
|
331
|
+
if (node.children.length > 0) {
|
|
332
|
+
const childCodes = node.children.map(c => genNode(c));
|
|
333
|
+
parts.push(`children: [\n${childCodes.map(c => '\t\t' + c).join(',\n')}\n\t]`);
|
|
334
|
+
}
|
|
335
|
+
const pathStr = JSON.stringify(node.fullPath);
|
|
336
|
+
const groupStr = node.isGroup ? `, isGroup: true` : '';
|
|
337
|
+
return `{ path: ${pathStr}${groupStr}, ${parts.join(', ')} }`;
|
|
338
|
+
}
|
|
339
|
+
const nodeCodes = tree.map(n => genNode(n));
|
|
340
|
+
const components = flattenSources(tree);
|
|
341
|
+
let code = `// Auto-generated route manifest — do not edit\n\n`;
|
|
342
|
+
for (const [name, sourcePath] of components) {
|
|
343
|
+
code += `import { ${name} } from '${prefix}${sourcePath}';\n`;
|
|
344
|
+
}
|
|
345
|
+
code += `\n`;
|
|
346
|
+
code += `const __routeTree = [\n`;
|
|
347
|
+
code += nodeCodes.map(c => '\t' + c).join(',\n');
|
|
348
|
+
code += `\n];\n\n`;
|
|
349
|
+
code += `export default __routeTree;\n`;
|
|
350
|
+
return code;
|
|
351
|
+
}
|
|
352
|
+
function flattenSources(tree) {
|
|
353
|
+
const map = new Map();
|
|
354
|
+
function walk(nodes) {
|
|
355
|
+
for (const node of nodes) {
|
|
356
|
+
if (node.page)
|
|
357
|
+
map.set(node.page, node.sourceDir + '/page.vsk');
|
|
358
|
+
if (node.layout)
|
|
359
|
+
map.set(node.layout, node.sourceDir + '/layout.vsk');
|
|
360
|
+
if (node.loading)
|
|
361
|
+
map.set(node.loading, node.sourceDir + '/loading.vsk');
|
|
362
|
+
if (node.error)
|
|
363
|
+
map.set(node.error, node.sourceDir + '/error.vsk');
|
|
364
|
+
if (node.notFound)
|
|
365
|
+
map.set(node.notFound, node.sourceDir + '/not-found.vsk');
|
|
366
|
+
walk(node.children);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
walk(tree);
|
|
370
|
+
return map;
|
|
371
|
+
}
|
|
372
|
+
export function matchUrl(tree, pathname) {
|
|
373
|
+
const parts = pathname.split('/').filter(Boolean);
|
|
374
|
+
const chain = [];
|
|
375
|
+
const params = {};
|
|
376
|
+
const rootNode = tree.find(n => n.fullPath === '/');
|
|
377
|
+
if (rootNode) {
|
|
378
|
+
chain.push(rootNode);
|
|
379
|
+
}
|
|
380
|
+
function matchNodes(nodes, partIndex) {
|
|
381
|
+
for (const node of nodes) {
|
|
382
|
+
if (node.isGroup) {
|
|
383
|
+
if (matchNodes(node.children, partIndex)) {
|
|
384
|
+
if (node.layout)
|
|
385
|
+
chain.push(node);
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if (node.fullPath === '/') {
|
|
391
|
+
return matchNodes(node.children, partIndex);
|
|
392
|
+
}
|
|
393
|
+
if (partIndex >= parts.length) {
|
|
394
|
+
if (node.page) {
|
|
395
|
+
chain.push(node);
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
const part = parts[partIndex];
|
|
401
|
+
if (node.isCatchAll) {
|
|
402
|
+
const paramName = node.path.startsWith(':') ? node.path.slice(1) : node.path;
|
|
403
|
+
params[paramName] = parts.slice(partIndex).map(decodeURIComponent).join('/');
|
|
404
|
+
chain.push(node);
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
if (node.isDynamic) {
|
|
408
|
+
const paramName = node.path.startsWith(':') ? node.path.slice(1) : node.path;
|
|
409
|
+
params[paramName] = decodeURIComponent(part);
|
|
410
|
+
chain.push(node);
|
|
411
|
+
if (node.children.length > 0) {
|
|
412
|
+
if (matchNodes(node.children, partIndex + 1))
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
else if (node.page) {
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
chain.pop();
|
|
419
|
+
delete params[paramName];
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
if (node.path === part) {
|
|
423
|
+
chain.push(node);
|
|
424
|
+
if (node.children.length > 0) {
|
|
425
|
+
if (matchNodes(node.children, partIndex + 1))
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
428
|
+
else if (node.page) {
|
|
429
|
+
return true;
|
|
430
|
+
}
|
|
431
|
+
chain.pop();
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
if (rootNode) {
|
|
438
|
+
const matched = matchNodes(rootNode.children, 0);
|
|
439
|
+
if (!matched && parts.length === 0) {
|
|
440
|
+
return { nodes: chain, params };
|
|
441
|
+
}
|
|
442
|
+
if (!matched)
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
const matched = matchNodes(tree, 0);
|
|
447
|
+
if (!matched)
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
450
|
+
return { nodes: chain, params };
|
|
451
|
+
}
|
package/dist/scan.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared character-level scanners for structured text that regex is too
|
|
3
|
+
* fragile to parse: balanced delimiters, strings, comments and top-level
|
|
4
|
+
* separators. Used where the input can contain nested delimiters, `>`
|
|
5
|
+
* inside quoted attributes or braces inside strings/comments.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isWhitespaceChar(ch: string): boolean;
|
|
8
|
+
export declare function isIdentStartCode(code: number): boolean;
|
|
9
|
+
export declare function isIdentCharCode(code: number): boolean;
|
|
10
|
+
export declare function isIdentStart(ch: string): boolean;
|
|
11
|
+
export declare function isIdentChar(ch: string): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* True when `text` (ignoring leading whitespace) begins with the whole
|
|
14
|
+
* identifier `ident` — the character after it is not an identifier char,
|
|
15
|
+
* and the character before its start is not an identifier char either.
|
|
16
|
+
*/
|
|
17
|
+
export declare function startsWithIdentifier(text: string, ident: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Strips a leading `const` / `let` / `var` keyword (plus surrounding
|
|
20
|
+
* whitespace) from a declaration prefix. Returns the remainder or the
|
|
21
|
+
* input unchanged when no keyword is present.
|
|
22
|
+
*/
|
|
23
|
+
export declare function stripDeclKeyword(text: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Removes trailing semicolons (and whitespace between them) from `text`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function stripTrailingSemicolons(text: string): string;
|
|
28
|
+
export declare function skipWhitespace(text: string, i: number): number;
|
|
29
|
+
/**
|
|
30
|
+
* Collapses runs starting at a newline into a single space (JSX text
|
|
31
|
+
* normalization): every `\n` plus any following whitespace becomes `' '`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function collapseNewlineWhitespace(text: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* Skips a string starting at `i` (`"`, `'` or `` ` `` — backticks keep
|
|
36
|
+
* nested `${...}` template depth). Returns the index just past the closing
|
|
37
|
+
* quote, or `text.length` when unterminated.
|
|
38
|
+
*/
|
|
39
|
+
export declare function skipString(text: string, i: number): number;
|
|
40
|
+
/**
|
|
41
|
+
* Skips a `//` or `/* ... */` comment starting at `i`. Returns the index
|
|
42
|
+
* just past the comment (past the newline for `//`), or `i` when no
|
|
43
|
+
* comment starts here.
|
|
44
|
+
*/
|
|
45
|
+
export declare function skipComment(text: string, i: number): number;
|
|
46
|
+
/**
|
|
47
|
+
* Given the index of an opening `(`, `[` or `{`, returns the index of its
|
|
48
|
+
* matching closing delimiter. Strings, `//` and `/* ... */` comments are
|
|
49
|
+
* skipped so delimiters inside them do not count. Returns `text.length`
|
|
50
|
+
* when the block is unterminated.
|
|
51
|
+
*/
|
|
52
|
+
export declare function findBalancedEnd(text: string, openIndex: number): number;
|
|
53
|
+
/**
|
|
54
|
+
* Splits `text` on every whole-identifier occurrence of `sep` that sits at
|
|
55
|
+
* bracket depth 0, outside strings and comments. The identifier must be a
|
|
56
|
+
* complete word (non-identifier characters on both sides), so `of` matches
|
|
57
|
+
* `for (x of y)` but not `xoff` or `info`. The surrounding separator text
|
|
58
|
+
* is not included in the parts.
|
|
59
|
+
*/
|
|
60
|
+
export declare function splitTopLevel(text: string, sep: string): string[];
|
|
61
|
+
/**
|
|
62
|
+
* True when `text` contains a `,` at bracket depth 0, outside strings and
|
|
63
|
+
* comments — i.e. a genuine multi-expression sequence rather than commas
|
|
64
|
+
* inside a call, object literal or parameter list.
|
|
65
|
+
*/
|
|
66
|
+
export declare function hasTopLevelComma(text: string): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* True when `text` contains a whole-identifier `of` or `in` at bracket
|
|
69
|
+
* depth 0, outside strings and comments — used to tell `for (... of ...)`
|
|
70
|
+
* and `for (... in ...)` headers apart from classic `for` loops.
|
|
71
|
+
*/
|
|
72
|
+
export declare function containsForOfIn(text: string): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Extracts the argument text of a `track<...>(...)` call from an init
|
|
75
|
+
* expression. Handles nested generics (`track<Map<string, number>>(...)`)
|
|
76
|
+
* and balanced parens in the argument. Returns the inner expression text,
|
|
77
|
+
* or the original input when the text is not a whole `track(...)` call.
|
|
78
|
+
*/
|
|
79
|
+
export declare function unwrapTrackCall(init: string): string;
|
|
80
|
+
/**
|
|
81
|
+
* Removes a leading `track<...>` generic clause from an init expression
|
|
82
|
+
* (client-side `track` calls have no generic args). Returns the input
|
|
83
|
+
* unchanged when it does not start with a `track<` generic.
|
|
84
|
+
*/
|
|
85
|
+
export declare function stripTrackGeneric(init: string): string;
|
|
86
|
+
export declare function skipTrackGeneric(text: string, open: number): number;
|
|
87
|
+
/**
|
|
88
|
+
* Scans a CSS block starting at `start` (an opening `{`) and returns the
|
|
89
|
+
* index just past its matching `}`. Quoted strings and `/* ... */`
|
|
90
|
+
* comments are skipped so braces inside them (e.g. `url("data:...{}")`)
|
|
91
|
+
* do not confuse the nesting count.
|
|
92
|
+
*/
|
|
93
|
+
export declare function cssBlockEnd(css: string, start: number): number;
|
|
94
|
+
/**
|
|
95
|
+
* Given the index of a `<` in HTML, returns the index just past the
|
|
96
|
+
* matching `>` — `>` inside quoted attribute values is not treated as a
|
|
97
|
+
* delimiter. Returns `-1` when the tag is unterminated.
|
|
98
|
+
*/
|
|
99
|
+
export declare function htmlTagEnd(html: string, lt: number): number;
|
|
100
|
+
/**
|
|
101
|
+
* Reads the tag name of an HTML token like `<div`, `</span>` or `<my-el`.
|
|
102
|
+
* Returns `null` when the token is not a tag. The tag name itself cannot
|
|
103
|
+
* contain quotes or `>` so a simple character scan suffices.
|
|
104
|
+
*/
|
|
105
|
+
export declare function htmlTagName(token: string): string | null;
|
|
106
|
+
//# sourceMappingURL=scan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../src/scan.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAItD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAE/C;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAOzE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAWrD;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS5D;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAG9D;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAc9D;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAsB1D;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAY3D;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAkBvE;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CA2BjE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAgBtD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAkBrD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAYpD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOtD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAcnE;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAqB9D;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAY3D;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAexD"}
|