@vesk/compiler 0.1.1 → 0.1.14
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/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/server-jsgen.d.ts.map +1 -1
- package/dist/server-jsgen.js +3 -2
- package/dist/server-render.d.ts.map +1 -1
- package/dist/server-render.js +2 -0
- package/dist/ssr-store.d.ts +1 -1
- package/dist/ssr-store.d.ts.map +1 -1
- package/dist/ssr-store.js +3 -0
- package/dist/typecheck.d.ts +2 -0
- package/dist/typecheck.d.ts.map +1 -1
- package/dist/typecheck.js +388 -4
- package/dist/vsk-tsx.d.ts +62 -0
- package/dist/vsk-tsx.d.ts.map +1 -1
- package/dist/vsk-tsx.js +831 -359
- package/package.json +1 -1
- package/dist/package.json +0 -15
package/dist/vsk-tsx.js
CHANGED
|
@@ -1,141 +1,49 @@
|
|
|
1
1
|
import { parse } from '@vesk/compiler/src/parser';
|
|
2
2
|
import { getPropsType } from '@vesk/compiler/src/ir-generator';
|
|
3
|
-
import { skipWhitespace, findBalancedEnd,
|
|
3
|
+
import { skipWhitespace, findBalancedEnd, startsWithIdentifier, stripDeclKeyword, stripTrailingSemicolons, isWhitespaceChar, isIdentStart, isIdentChar, htmlTagEnd, } from '@vesk/compiler/src/scan';
|
|
4
4
|
import { containsIdentifier, isIdentifierImported } from '@vesk/compiler/src/tokens';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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);
|
|
5
|
+
const FULL_DATA = {
|
|
6
|
+
verification: true,
|
|
7
|
+
completion: true,
|
|
8
|
+
semantic: true,
|
|
9
|
+
navigation: true,
|
|
10
|
+
structure: true,
|
|
11
|
+
format: true,
|
|
12
|
+
};
|
|
13
|
+
const REACTIVE_DATA = { ...FULL_DATA, customData: { vesk: { reactive: true } } };
|
|
14
|
+
class VskGen {
|
|
15
|
+
code = '';
|
|
16
|
+
mappings = [];
|
|
17
|
+
styleRegions = [];
|
|
18
|
+
/** Counts track-decl rewrites so typedCells cell names stay unique within a codegen run. */
|
|
19
|
+
cellCount = 0;
|
|
20
|
+
/** Appends `text`; when a source range is given, records a mapping. */
|
|
21
|
+
add(text, srcStart, srcEnd, data = FULL_DATA) {
|
|
22
|
+
const start = this.code.length;
|
|
23
|
+
this.code += text;
|
|
24
|
+
if (text.length > 0 && srcStart !== undefined && srcEnd !== undefined && srcEnd > srcStart) {
|
|
25
|
+
this.mappings.push({
|
|
26
|
+
sourceOffsets: [srcStart],
|
|
27
|
+
generatedOffsets: [start],
|
|
28
|
+
lengths: [srcEnd - srcStart],
|
|
29
|
+
generatedLengths: [text.length],
|
|
30
|
+
data,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
109
33
|
}
|
|
110
|
-
|
|
111
|
-
|
|
34
|
+
/** Appends synthetic text with no source mapping. */
|
|
35
|
+
addRaw(text) {
|
|
36
|
+
this.code += text;
|
|
112
37
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
}
|
|
38
|
+
prepend(text) {
|
|
39
|
+
this.code = text + this.code;
|
|
40
|
+
for (const m of this.mappings) {
|
|
41
|
+
m.generatedOffsets = m.generatedOffsets.map((o) => o + text.length);
|
|
134
42
|
}
|
|
135
|
-
out.push(text[i]);
|
|
136
|
-
i++;
|
|
137
43
|
}
|
|
138
|
-
|
|
44
|
+
recordStyle(start, end, content) {
|
|
45
|
+
this.styleRegions.push({ start, end, content });
|
|
46
|
+
}
|
|
139
47
|
}
|
|
140
48
|
function getComponents(ast) {
|
|
141
49
|
const out = [];
|
|
@@ -183,15 +91,384 @@ function getJSXTagName(el) {
|
|
|
183
91
|
return name.namespace.name + ':' + name.name.name;
|
|
184
92
|
return null;
|
|
185
93
|
}
|
|
186
|
-
|
|
94
|
+
/** Collapses newline+whitespace runs to single spaces and records the raw index of every kept character. */
|
|
95
|
+
function collapseWithMap(value) {
|
|
96
|
+
let out = '';
|
|
97
|
+
const map = [];
|
|
98
|
+
let i = 0;
|
|
99
|
+
while (i < value.length) {
|
|
100
|
+
const c = value[i];
|
|
101
|
+
if (c === '\n') {
|
|
102
|
+
out += ' ';
|
|
103
|
+
map.push(i);
|
|
104
|
+
while (i < value.length && isWhitespaceChar(value[i]))
|
|
105
|
+
i++;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
out += c;
|
|
109
|
+
map.push(i);
|
|
110
|
+
i++;
|
|
111
|
+
}
|
|
112
|
+
return { text: out, map };
|
|
113
|
+
}
|
|
114
|
+
function isStatementMode(node) {
|
|
115
|
+
const stmts = node.body?.body ?? [];
|
|
116
|
+
return !(stmts.length === 1 && stmts[0].type === 'ReturnStatement');
|
|
117
|
+
}
|
|
118
|
+
/** Index of the first whole-word occurrence of `word` in `text`, or -1. */
|
|
119
|
+
function findFirstKeyword(text, word) {
|
|
120
|
+
let i = 0;
|
|
121
|
+
while (i < text.length) {
|
|
122
|
+
if (isIdentStart(text[i])) {
|
|
123
|
+
let j = i + 1;
|
|
124
|
+
while (j < text.length && isIdentChar(text[j]))
|
|
125
|
+
j++;
|
|
126
|
+
if (text.slice(i, j) === word)
|
|
127
|
+
return i;
|
|
128
|
+
i = j;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
i++;
|
|
132
|
+
}
|
|
133
|
+
return -1;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Removes a trailing `client` keyword (plus surrounding whitespace) from a
|
|
137
|
+
* component header, replacing it with a single trailing space. Returns the
|
|
138
|
+
* kept text and the length of the source range it covers.
|
|
139
|
+
*/
|
|
140
|
+
function stripTrailingClientRange(text) {
|
|
141
|
+
let end = text.length;
|
|
142
|
+
while (end > 0 && isWhitespaceChar(text[end - 1]))
|
|
143
|
+
end--;
|
|
144
|
+
let wordStart = end;
|
|
145
|
+
while (wordStart > 0 && isIdentChar(text[wordStart - 1]))
|
|
146
|
+
wordStart--;
|
|
147
|
+
if (text.slice(wordStart, end) === 'client') {
|
|
148
|
+
return { text: text.slice(0, wordStart).trimEnd() + ' ', end: wordStart };
|
|
149
|
+
}
|
|
150
|
+
return { text, end: text.length };
|
|
151
|
+
}
|
|
152
|
+
/** Index of the first depth-0 whole-word occurrence of `sep` in `text`, or -1 (splitTopLevel semantics). */
|
|
153
|
+
function findTopLevelSep(text, sep) {
|
|
154
|
+
let depth = 0;
|
|
155
|
+
let i = 0;
|
|
156
|
+
while (i < text.length) {
|
|
157
|
+
const c = text[i];
|
|
158
|
+
if (c === '"' || c === "'" || c === '`') {
|
|
159
|
+
let j = i + 1;
|
|
160
|
+
while (j < text.length) {
|
|
161
|
+
const q = text[j];
|
|
162
|
+
if (q === '\\') {
|
|
163
|
+
j += 2;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (q === c) {
|
|
167
|
+
j++;
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
j++;
|
|
171
|
+
}
|
|
172
|
+
i = j;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (c === '/' && (text[i + 1] === '/' || text[i + 1] === '*')) {
|
|
176
|
+
let j = i + 2;
|
|
177
|
+
if (text[i + 1] === '/') {
|
|
178
|
+
while (j < text.length && text[j] !== '\n')
|
|
179
|
+
j++;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
while (j < text.length && !(text[j] === '*' && text[j + 1] === '/'))
|
|
183
|
+
j++;
|
|
184
|
+
j = Math.min(j + 2, text.length);
|
|
185
|
+
}
|
|
186
|
+
i = j;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (c === '(' || c === '[' || c === '{')
|
|
190
|
+
depth++;
|
|
191
|
+
else if (c === ')' || c === ']' || c === '}') {
|
|
192
|
+
if (depth > 0)
|
|
193
|
+
depth--;
|
|
194
|
+
}
|
|
195
|
+
else if (depth === 0 && c === sep[0] && text.slice(i, i + sep.length) === sep) {
|
|
196
|
+
const before = i === 0 ? ' ' : text[i - 1];
|
|
197
|
+
const after = i + sep.length < text.length ? text[i + sep.length] : ' ';
|
|
198
|
+
if (!isIdentChar(before) && !isIdentChar(after))
|
|
199
|
+
return i;
|
|
200
|
+
}
|
|
201
|
+
i++;
|
|
202
|
+
}
|
|
203
|
+
return -1;
|
|
204
|
+
}
|
|
205
|
+
function trailingWsLen(text) {
|
|
206
|
+
let n = 0;
|
|
207
|
+
while (n < text.length && isWhitespaceChar(text[text.length - 1 - n]))
|
|
208
|
+
n++;
|
|
209
|
+
return n;
|
|
210
|
+
}
|
|
211
|
+
function leadingWsLen(text) {
|
|
212
|
+
let n = 0;
|
|
213
|
+
while (n < text.length && isWhitespaceChar(text[n]))
|
|
214
|
+
n++;
|
|
215
|
+
return n;
|
|
216
|
+
}
|
|
217
|
+
function isTrackDeclStatement(stmt) {
|
|
218
|
+
if (!stmt || stmt.type !== 'VariableDeclaration')
|
|
219
|
+
return false;
|
|
220
|
+
return (stmt.declarations || []).some((d) => d && d.id && d.id.type === 'ArrayPattern' && d.id.lazy === true && d.init);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Emits the rewrite for a `const &[count] = track(0)` declarator. Mirrors
|
|
224
|
+
* the exact text produced by the previous string-level rewrite: the first
|
|
225
|
+
* declarator absorbs the statement's `const`/`let`/`var` keyword, the
|
|
226
|
+
* replacement always ends with `;` unless the source statement already
|
|
227
|
+
* provides a terminator (checked on the emitted statement text, exactly
|
|
228
|
+
* like the old `wrapped[end] === ';'` rule).
|
|
229
|
+
*/
|
|
230
|
+
function emitTrackDeclStatement(g, source, stmt, indent, isLast, opts) {
|
|
231
|
+
const fullText = stripTrailingSemicolons(source.slice(stmt.start, stmt.end).trimEnd());
|
|
232
|
+
const text = fullText + (!fullText.endsWith('}') && !isLast ? ';' : '');
|
|
233
|
+
const decls = stmt.declarations || [];
|
|
234
|
+
g.add(indent);
|
|
235
|
+
let prevEnd = stmt.start;
|
|
236
|
+
for (let di = 0; di < decls.length; di++) {
|
|
237
|
+
const decl = decls[di];
|
|
238
|
+
const id = decl.id;
|
|
239
|
+
const isLazy = id && id.type === 'ArrayPattern' && id.lazy === true && decl.init;
|
|
240
|
+
if (!isLazy) {
|
|
241
|
+
g.addRaw(source.slice(prevEnd, decl.start));
|
|
242
|
+
g.add(source.slice(decl.start, decl.end), decl.start, decl.end);
|
|
243
|
+
prevEnd = decl.end;
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (di > 0)
|
|
247
|
+
g.addRaw(source.slice(prevEnd, decl.start));
|
|
248
|
+
const ch = text[decl.end - stmt.start] ?? ' ';
|
|
249
|
+
const elements = (id.elements || []);
|
|
250
|
+
const names = elements
|
|
251
|
+
.map((el) => (el ? el.name : null))
|
|
252
|
+
.filter((n) => typeof n === 'string');
|
|
253
|
+
const initText = source.slice(decl.init.start, decl.init.end);
|
|
254
|
+
if (names.length === 0) {
|
|
255
|
+
g.add('let _ = ');
|
|
256
|
+
g.add(initText, decl.init.start, decl.init.end);
|
|
257
|
+
if (ch !== ';')
|
|
258
|
+
g.addRaw(';');
|
|
259
|
+
prevEnd = decl.end;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const annotation = id.typeAnnotation?.typeAnnotation
|
|
263
|
+
? source.slice(id.typeAnnotation.typeAnnotation.start, id.typeAnnotation.typeAnnotation.end).trim()
|
|
264
|
+
: decl.init.typeArguments
|
|
265
|
+
? source.slice(decl.init.typeArguments.start + 1, decl.init.typeArguments.end - 1).trim()
|
|
266
|
+
: '';
|
|
267
|
+
const annStart = id.typeAnnotation?.typeAnnotation
|
|
268
|
+
? id.typeAnnotation.typeAnnotation.start
|
|
269
|
+
: decl.init.typeArguments
|
|
270
|
+
? decl.init.typeArguments.start + 1
|
|
271
|
+
: -1;
|
|
272
|
+
const annEnd = id.typeAnnotation?.typeAnnotation
|
|
273
|
+
? id.typeAnnotation.typeAnnotation.end
|
|
274
|
+
: decl.init.typeArguments
|
|
275
|
+
? decl.init.typeArguments.end - 1
|
|
276
|
+
: -1;
|
|
277
|
+
const first = names[0];
|
|
278
|
+
const firstRange = [elements[0].start, elements[0].end];
|
|
279
|
+
const dropLastSemi = ch === ';';
|
|
280
|
+
if (opts.typedCells) {
|
|
281
|
+
const cellName = g.cellCount === 0 ? '__cell' : `__cell${g.cellCount}`;
|
|
282
|
+
g.cellCount++;
|
|
283
|
+
// The cell holds a Tracked<T>, NOT a T — leave its type to inference
|
|
284
|
+
// from `track<T>(init)` so raw-cell aliases stay correctly typed too.
|
|
285
|
+
// Annotating it with the value annotation made `&[v, cell]` emit
|
|
286
|
+
// `cell: T = <Tracked>` (unsound), and broke passing `cell` to APIs
|
|
287
|
+
// expecting `Tracked<unknown>` (e.g. useFetch `into`).
|
|
288
|
+
g.add(`const ${cellName} = `);
|
|
289
|
+
g.add(initText, decl.init.start, decl.init.end);
|
|
290
|
+
g.add(';');
|
|
291
|
+
// First binding: the VALUE read from the cell.
|
|
292
|
+
g.add(' let ');
|
|
293
|
+
g.add(first, firstRange[0], firstRange[1], REACTIVE_DATA);
|
|
294
|
+
if (annotation) {
|
|
295
|
+
g.add(': ');
|
|
296
|
+
g.add(annotation, annStart, annEnd);
|
|
297
|
+
}
|
|
298
|
+
g.add(` = ${cellName}.get();`);
|
|
299
|
+
// Raw-cell aliases: inferred Tracked<T>, never the value annotation.
|
|
300
|
+
for (let n = 1; n < names.length; n++) {
|
|
301
|
+
g.add(' let ');
|
|
302
|
+
g.add(names[n], elements[n].start, elements[n].end, REACTIVE_DATA);
|
|
303
|
+
g.add(` = ${cellName};`);
|
|
304
|
+
}
|
|
305
|
+
if (dropLastSemi)
|
|
306
|
+
g.code = g.code.slice(0, -1);
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
if (annotation) {
|
|
310
|
+
g.add('let ');
|
|
311
|
+
g.add(first, firstRange[0], firstRange[1], REACTIVE_DATA);
|
|
312
|
+
g.add(': ');
|
|
313
|
+
g.add(annotation, annStart, annEnd);
|
|
314
|
+
g.add(' = (');
|
|
315
|
+
g.add(initText, decl.init.start, decl.init.end);
|
|
316
|
+
g.add(' as unknown as ');
|
|
317
|
+
g.add(annotation, annStart, annEnd);
|
|
318
|
+
g.add(');');
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
g.add('let ');
|
|
322
|
+
g.add(first, firstRange[0], firstRange[1], REACTIVE_DATA);
|
|
323
|
+
g.add(': any = ');
|
|
324
|
+
g.add(initText, decl.init.start, decl.init.end);
|
|
325
|
+
g.add(';');
|
|
326
|
+
}
|
|
327
|
+
for (let n = 1; n < names.length; n++) {
|
|
328
|
+
g.add(' let ');
|
|
329
|
+
g.add(names[n], elements[n].start, elements[n].end, REACTIVE_DATA);
|
|
330
|
+
g.add(': any = ');
|
|
331
|
+
g.add(first, firstRange[0], firstRange[1]);
|
|
332
|
+
g.add(';');
|
|
333
|
+
if (n === names.length - 1 && dropLastSemi)
|
|
334
|
+
g.code = g.code.slice(0, -1);
|
|
335
|
+
}
|
|
336
|
+
if (names.length === 1 && dropLastSemi)
|
|
337
|
+
g.code = g.code.slice(0, -1);
|
|
338
|
+
}
|
|
339
|
+
prevEnd = decl.end;
|
|
340
|
+
}
|
|
341
|
+
if (!isLast)
|
|
342
|
+
g.addRaw(';');
|
|
343
|
+
}
|
|
344
|
+
function emitBlock(g, source, body, indent, opts) {
|
|
187
345
|
const stmts = body && body.type === 'BlockStatement'
|
|
188
346
|
? body.body
|
|
189
347
|
: body ? (Array.isArray(body) ? body : [body]) : [];
|
|
190
|
-
|
|
191
|
-
|
|
348
|
+
g.addRaw('{\n');
|
|
349
|
+
emitBody(g, source, stmts, indent + ' ', opts);
|
|
350
|
+
g.addRaw('\n');
|
|
351
|
+
g.add(indent);
|
|
352
|
+
g.addRaw('}');
|
|
192
353
|
}
|
|
193
|
-
function
|
|
194
|
-
|
|
354
|
+
function emitJSXAttr(g, source, attr) {
|
|
355
|
+
if (attr.type === 'JSXSpreadAttribute') {
|
|
356
|
+
g.add(source.slice(attr.start, attr.end), attr.start, attr.end);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
const name = attr.name;
|
|
360
|
+
g.add(source.slice(name.start, name.end), name.start, name.end);
|
|
361
|
+
const v = attr.value;
|
|
362
|
+
if (!v)
|
|
363
|
+
return;
|
|
364
|
+
g.add(source.slice(name.end, v.start), name.end, v.start);
|
|
365
|
+
if (v.type === 'JSXExpressionContainer') {
|
|
366
|
+
g.add(source.slice(v.start, v.expression.start), v.start, v.expression.start);
|
|
367
|
+
g.add(source.slice(v.expression.start, v.expression.end), v.expression.start, v.expression.end);
|
|
368
|
+
g.add(source.slice(v.expression.end, v.end), v.expression.end, v.end);
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
g.add(source.slice(v.start, v.end), v.start, v.end);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function emitJSXElement(g, source, el, opts) {
|
|
375
|
+
const op = el.openingElement;
|
|
376
|
+
const name = op.name;
|
|
377
|
+
let prev;
|
|
378
|
+
if (name) {
|
|
379
|
+
g.add(source.slice(op.start, name.start), op.start, name.start);
|
|
380
|
+
g.add(source.slice(name.start, name.end), name.start, name.end);
|
|
381
|
+
prev = name.end;
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
prev = op.start + 1;
|
|
385
|
+
}
|
|
386
|
+
for (const attr of op.attributes ?? []) {
|
|
387
|
+
g.add(source.slice(prev, attr.start), prev, attr.start);
|
|
388
|
+
emitJSXAttr(g, source, attr);
|
|
389
|
+
prev = attr.end;
|
|
390
|
+
}
|
|
391
|
+
g.add(source.slice(prev, op.end), prev, op.end);
|
|
392
|
+
if (op.selfClosing)
|
|
393
|
+
return;
|
|
394
|
+
emitJSXChildren(g, source, el.children ?? [], opts);
|
|
395
|
+
if (el.closingElement) {
|
|
396
|
+
g.add(source.slice(el.closingElement.start, el.closingElement.end), el.closingElement.start, el.closingElement.end);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
function emitJSXExprBody(g, source, expr, opts) {
|
|
400
|
+
if (expr.type === 'JSXElement')
|
|
401
|
+
return emitJSXElement(g, source, expr, opts);
|
|
402
|
+
if (expr.type === 'JSXFragment')
|
|
403
|
+
return emitJSXFragment(g, source, expr, opts);
|
|
404
|
+
g.add(source.slice(expr.start, expr.end), expr.start, expr.end);
|
|
405
|
+
}
|
|
406
|
+
function emitJSXFragment(g, source, frag, opts) {
|
|
407
|
+
g.addRaw('<>');
|
|
408
|
+
emitJSXChildren(g, source, frag.children ?? [], opts);
|
|
409
|
+
g.addRaw('</>');
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Emits the `.map(...)` scaffolding for a `for (... of ...)` / `for (... in
|
|
413
|
+
* ...)` clause inside JSX text, mapping the array/object expression to its
|
|
414
|
+
* source range. `clauseStart` is the collapsed-text offset of the clause's
|
|
415
|
+
* first character (inside `text`) and `map` translates collapsed offsets
|
|
416
|
+
* back to raw source offsets.
|
|
417
|
+
*/
|
|
418
|
+
function emitForClauseMap(g, source, child, text, map, clauseStart, bodyEmit, emptyEmit) {
|
|
419
|
+
const clauseEnd = findBalancedEnd(text, clauseStart - 1);
|
|
420
|
+
const clause = text.slice(clauseStart, clauseEnd);
|
|
421
|
+
const ofIdx = findTopLevelSep(clause, 'of');
|
|
422
|
+
const inIdx = ofIdx === -1 ? findTopLevelSep(clause, 'in') : -1;
|
|
423
|
+
if (ofIdx === -1 && inIdx === -1)
|
|
424
|
+
return false;
|
|
425
|
+
const sepIdx = ofIdx === -1 ? inIdx : ofIdx;
|
|
426
|
+
const lhs = clause.slice(0, sepIdx);
|
|
427
|
+
const rhs = clause.slice(sepIdx + 2);
|
|
428
|
+
const arrExpr = rhs.trim();
|
|
429
|
+
const arrStartCollapsed = clauseStart + sepIdx + 2 + leadingWsLen(rhs);
|
|
430
|
+
const arrEndCollapsed = clauseStart + sepIdx + 2 + rhs.length - trailingWsLen(rhs);
|
|
431
|
+
const arrStart = arrStartCollapsed < map.length ? child.start + map[arrStartCollapsed] : child.start;
|
|
432
|
+
const arrEnd = arrEndCollapsed > 0 && arrEndCollapsed - 1 < map.length
|
|
433
|
+
? child.start + map[arrEndCollapsed - 1] + 1
|
|
434
|
+
: child.start;
|
|
435
|
+
const itemVar = stripDeclKeyword(lhs).trim();
|
|
436
|
+
if (ofIdx !== -1) {
|
|
437
|
+
if (emptyEmit) {
|
|
438
|
+
g.addRaw('{ (() => { const _v = ');
|
|
439
|
+
g.add(arrExpr, arrStart, arrEnd);
|
|
440
|
+
g.addRaw('; return _v.length === 0 ? (');
|
|
441
|
+
emptyEmit();
|
|
442
|
+
g.addRaw(') : _v.map((');
|
|
443
|
+
g.addRaw(itemVar);
|
|
444
|
+
g.addRaw(': (typeof _v)[number]) => (');
|
|
445
|
+
bodyEmit();
|
|
446
|
+
g.addRaw(')); })() }');
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
g.addRaw('{ ');
|
|
450
|
+
g.add(arrExpr, arrStart, arrEnd);
|
|
451
|
+
g.addRaw('.map((');
|
|
452
|
+
g.addRaw(itemVar);
|
|
453
|
+
g.addRaw(': (typeof ');
|
|
454
|
+
g.add(arrExpr, arrStart, arrEnd);
|
|
455
|
+
g.addRaw(')[number]) => (');
|
|
456
|
+
bodyEmit();
|
|
457
|
+
g.addRaw(')) }');
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
g.addRaw('{ Object.keys(');
|
|
462
|
+
g.add(arrExpr, arrStart, arrEnd);
|
|
463
|
+
g.addRaw(').map((');
|
|
464
|
+
g.addRaw(itemVar);
|
|
465
|
+
g.addRaw(') => (');
|
|
466
|
+
bodyEmit();
|
|
467
|
+
g.addRaw(')) }');
|
|
468
|
+
}
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
function emitJSXChildren(g, source, children, opts) {
|
|
195
472
|
let i = 0;
|
|
196
473
|
let forPending = false;
|
|
197
474
|
while (i < children.length) {
|
|
@@ -199,7 +476,7 @@ function tsxEmitJSXChildren(source, children) {
|
|
|
199
476
|
if (child.type !== 'JSXText')
|
|
200
477
|
forPending = false;
|
|
201
478
|
if (child.type === 'JSXText') {
|
|
202
|
-
const text =
|
|
479
|
+
const { text, map } = collapseWithMap(child.value);
|
|
203
480
|
const trimmed = text.trim();
|
|
204
481
|
if (!trimmed || trimmed.startsWith('//')) {
|
|
205
482
|
i++;
|
|
@@ -210,56 +487,42 @@ function tsxEmitJSXChildren(source, children) {
|
|
|
210
487
|
if (forDecl !== null && nextChild !== undefined && (nextChild.type === 'JSXExpressionContainer' ||
|
|
211
488
|
nextChild.type === 'JSXElement' ||
|
|
212
489
|
nextChild.type === 'JSXFragment')) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
490
|
+
const trimmedStartCollapsed = leadingWsLen(text);
|
|
491
|
+
const parenIdx = skipWhitespace(text, trimmedStartCollapsed + 3);
|
|
492
|
+
if (text[parenIdx] === '(') {
|
|
493
|
+
const clauseStartCollapsed = parenIdx + 1;
|
|
494
|
+
let consumed = 2;
|
|
495
|
+
let emptyEmit = null;
|
|
496
|
+
const emptyTextNode = children[i + 2];
|
|
497
|
+
const emptyContainer = children[i + 3];
|
|
498
|
+
if (emptyTextNode && emptyTextNode.type === 'JSXText' &&
|
|
499
|
+
['#empty', 'empty'].includes(emptyTextNode.value.trim()) &&
|
|
500
|
+
emptyContainer && emptyContainer.type === 'JSXExpressionContainer' &&
|
|
501
|
+
emptyContainer.expression.type !== 'JSXEmptyExpression') {
|
|
502
|
+
consumed = 4;
|
|
503
|
+
emptyEmit = () => emitJSXExprBody(g, source, emptyContainer.expression, opts);
|
|
220
504
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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})); })() }`);
|
|
505
|
+
let matched = false;
|
|
506
|
+
if (nextChild.type === 'JSXExpressionContainer') {
|
|
507
|
+
const exprNode = nextChild.expression;
|
|
508
|
+
if (exprNode.type === 'JSXEmptyExpression') {
|
|
509
|
+
g.add(text, child.start, child.end);
|
|
510
|
+
i++;
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
matched = emitForClauseMap(g, source, child, text, map, clauseStartCollapsed, () => emitJSXExprBody(g, source, exprNode, opts), emptyEmit);
|
|
514
|
+
}
|
|
515
|
+
else if (nextChild.type === 'JSXFragment') {
|
|
516
|
+
matched = emitForClauseMap(g, source, child, text, map, clauseStartCollapsed, () => emitJSXFragment(g, source, nextChild, opts), emptyEmit);
|
|
247
517
|
}
|
|
248
518
|
else {
|
|
249
|
-
|
|
519
|
+
matched = emitForClauseMap(g, source, child, text, map, clauseStartCollapsed, () => emitJSXElement(g, source, nextChild, opts), emptyEmit);
|
|
520
|
+
}
|
|
521
|
+
if (matched) {
|
|
522
|
+
i += consumed;
|
|
523
|
+
forPending = true;
|
|
524
|
+
continue;
|
|
250
525
|
}
|
|
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
526
|
}
|
|
264
527
|
}
|
|
265
528
|
if (forPending && trimmed === '}') {
|
|
@@ -268,7 +531,7 @@ function tsxEmitJSXChildren(source, children) {
|
|
|
268
531
|
continue;
|
|
269
532
|
}
|
|
270
533
|
forPending = false;
|
|
271
|
-
|
|
534
|
+
g.add(text, child.start, child.end);
|
|
272
535
|
i++;
|
|
273
536
|
}
|
|
274
537
|
else if (child.type === 'JSXExpressionContainer') {
|
|
@@ -276,165 +539,227 @@ function tsxEmitJSXChildren(source, children) {
|
|
|
276
539
|
i++;
|
|
277
540
|
continue;
|
|
278
541
|
}
|
|
279
|
-
|
|
542
|
+
g.addRaw('{');
|
|
543
|
+
g.add(source.slice(child.expression.start, child.expression.end), child.expression.start, child.expression.end);
|
|
544
|
+
g.addRaw('}');
|
|
280
545
|
i++;
|
|
281
546
|
}
|
|
282
547
|
else if (child.type === 'JSXElement') {
|
|
283
548
|
if (getJSXTagName(child) === 'style') {
|
|
549
|
+
const openEnd = child.openingElement?.end ?? child.start;
|
|
550
|
+
const closeStart = child.closingElement?.start ?? child.end;
|
|
551
|
+
g.recordStyle(child.start, child.end, source.slice(openEnd, closeStart));
|
|
284
552
|
i++;
|
|
285
553
|
continue;
|
|
286
554
|
}
|
|
287
|
-
|
|
555
|
+
emitJSXElement(g, source, child, opts);
|
|
288
556
|
i++;
|
|
289
557
|
}
|
|
290
558
|
else if (child.type === 'JSXFragment') {
|
|
291
|
-
|
|
559
|
+
emitJSXFragment(g, source, child, opts);
|
|
292
560
|
i++;
|
|
293
561
|
}
|
|
294
562
|
else {
|
|
295
563
|
i++;
|
|
296
564
|
}
|
|
297
565
|
}
|
|
298
|
-
return parts.join('');
|
|
299
566
|
}
|
|
300
|
-
function
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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)}`;
|
|
567
|
+
function emitIf(g, source, stmt, indent, opts) {
|
|
568
|
+
g.add(indent);
|
|
569
|
+
g.addRaw('if (');
|
|
570
|
+
g.add(source.slice(stmt.test.start, stmt.test.end), stmt.test.start, stmt.test.end);
|
|
571
|
+
g.addRaw(') ');
|
|
572
|
+
emitBlock(g, source, stmt.consequent, indent, opts);
|
|
322
573
|
if (stmt.alternate) {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
: tsxBlock(source, [stmt.alternate], indent);
|
|
326
|
-
out += ` else ${altBlock}`;
|
|
574
|
+
g.addRaw(' else ');
|
|
575
|
+
emitBlock(g, source, stmt.alternate, indent, opts);
|
|
327
576
|
}
|
|
328
|
-
return out;
|
|
329
577
|
}
|
|
330
|
-
function
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
578
|
+
function emitForOf(g, source, stmt, indent, opts) {
|
|
579
|
+
g.add(indent);
|
|
580
|
+
g.addRaw('for (');
|
|
581
|
+
g.add(source.slice(stmt.left.start, stmt.left.end), stmt.left.start, stmt.left.end);
|
|
582
|
+
g.addRaw(' of ');
|
|
583
|
+
g.add(source.slice(stmt.right.start, stmt.right.end), stmt.right.start, stmt.right.end);
|
|
584
|
+
g.addRaw(') ');
|
|
585
|
+
emitBlock(g, source, stmt.body, indent, opts);
|
|
586
|
+
}
|
|
587
|
+
function emitForIn(g, source, stmt, indent, opts) {
|
|
588
|
+
g.add(indent);
|
|
589
|
+
g.addRaw('for (');
|
|
590
|
+
g.add(source.slice(stmt.left.start, stmt.left.end), stmt.left.start, stmt.left.end);
|
|
591
|
+
g.addRaw(' in ');
|
|
592
|
+
g.add(source.slice(stmt.right.start, stmt.right.end), stmt.right.start, stmt.right.end);
|
|
593
|
+
g.addRaw(') ');
|
|
594
|
+
emitBlock(g, source, stmt.body, indent, opts);
|
|
595
|
+
}
|
|
596
|
+
function emitFor(g, source, stmt, indent, opts) {
|
|
597
|
+
g.add(indent);
|
|
598
|
+
g.addRaw('for (');
|
|
599
|
+
if (stmt.init)
|
|
600
|
+
g.add(source.slice(stmt.init.start, stmt.init.end), stmt.init.start, stmt.init.end);
|
|
601
|
+
g.addRaw('; ');
|
|
602
|
+
if (stmt.test)
|
|
603
|
+
g.add(source.slice(stmt.test.start, stmt.test.end), stmt.test.start, stmt.test.end);
|
|
604
|
+
g.addRaw('; ');
|
|
605
|
+
if (stmt.update)
|
|
606
|
+
g.add(source.slice(stmt.update.start, stmt.update.end), stmt.update.start, stmt.update.end);
|
|
607
|
+
g.addRaw(') ');
|
|
608
|
+
emitBlock(g, source, stmt.body, indent, opts);
|
|
609
|
+
}
|
|
610
|
+
function emitWhile(g, source, stmt, indent, opts) {
|
|
611
|
+
g.add(indent);
|
|
612
|
+
g.addRaw('while (');
|
|
613
|
+
g.add(source.slice(stmt.test.start, stmt.test.end), stmt.test.start, stmt.test.end);
|
|
614
|
+
g.addRaw(') ');
|
|
615
|
+
emitBlock(g, source, stmt.body, indent, opts);
|
|
616
|
+
}
|
|
617
|
+
function emitDoWhile(g, source, stmt, indent, opts) {
|
|
618
|
+
g.add(indent);
|
|
619
|
+
g.addRaw('do ');
|
|
620
|
+
emitBlock(g, source, stmt.body, indent, opts);
|
|
621
|
+
g.addRaw(' while (');
|
|
622
|
+
g.add(source.slice(stmt.test.start, stmt.test.end), stmt.test.start, stmt.test.end);
|
|
623
|
+
g.addRaw(');');
|
|
624
|
+
}
|
|
625
|
+
function emitSwitch(g, source, stmt, indent, opts) {
|
|
626
|
+
g.add(indent);
|
|
627
|
+
g.addRaw('switch (');
|
|
628
|
+
g.add(source.slice(stmt.discriminant.start, stmt.discriminant.end), stmt.discriminant.start, stmt.discriminant.end);
|
|
629
|
+
g.addRaw(') {');
|
|
630
|
+
let firstCase = true;
|
|
357
631
|
for (const c of stmt.cases ?? []) {
|
|
632
|
+
g.addRaw(firstCase ? '\n' : '\n');
|
|
633
|
+
firstCase = false;
|
|
634
|
+
g.add(indent);
|
|
635
|
+
g.addRaw(' ');
|
|
358
636
|
if (c.test) {
|
|
359
|
-
|
|
637
|
+
g.addRaw('case ');
|
|
638
|
+
g.add(source.slice(c.test.start, c.test.end), c.test.start, c.test.end);
|
|
639
|
+
g.addRaw(':');
|
|
360
640
|
}
|
|
361
641
|
else {
|
|
362
|
-
|
|
642
|
+
g.addRaw('default:');
|
|
363
643
|
}
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
644
|
+
const before = g.code.length;
|
|
645
|
+
emitBody(g, source, c.consequent, indent + ' ', opts);
|
|
646
|
+
if (g.code.length > before)
|
|
647
|
+
g.addRaw('\n');
|
|
367
648
|
}
|
|
368
|
-
|
|
369
|
-
|
|
649
|
+
g.addRaw('\n');
|
|
650
|
+
g.add(indent);
|
|
651
|
+
g.addRaw('}');
|
|
370
652
|
}
|
|
371
|
-
function
|
|
372
|
-
|
|
653
|
+
function emitTry(g, source, stmt, indent, opts) {
|
|
654
|
+
g.add(indent);
|
|
655
|
+
g.addRaw('try ');
|
|
656
|
+
emitBlock(g, source, stmt.block, indent, opts);
|
|
373
657
|
if (stmt.handler) {
|
|
374
|
-
|
|
375
|
-
|
|
658
|
+
g.addRaw(' catch');
|
|
659
|
+
if (stmt.handler.param) {
|
|
660
|
+
g.addRaw(' (');
|
|
661
|
+
g.add(source.slice(stmt.handler.param.start, stmt.handler.param.end), stmt.handler.param.start, stmt.handler.param.end);
|
|
662
|
+
g.addRaw(')');
|
|
663
|
+
}
|
|
664
|
+
g.addRaw(' ');
|
|
665
|
+
emitBlock(g, source, stmt.handler.body, indent, opts);
|
|
376
666
|
}
|
|
377
667
|
if (stmt.finalizer) {
|
|
378
|
-
|
|
668
|
+
g.addRaw(' finally ');
|
|
669
|
+
emitBlock(g, source, stmt.finalizer, indent, opts);
|
|
379
670
|
}
|
|
380
|
-
return out;
|
|
381
671
|
}
|
|
382
|
-
function
|
|
672
|
+
function emitPlainStatement(g, source, stmt, indent, isLast) {
|
|
673
|
+
let text = stripTrailingSemicolons(source.slice(stmt.start, stmt.end).trimEnd());
|
|
674
|
+
if (text === '')
|
|
675
|
+
return;
|
|
676
|
+
g.add(indent);
|
|
677
|
+
g.add(text, stmt.start, stmt.end);
|
|
678
|
+
if (!text.endsWith('}') && !isLast)
|
|
679
|
+
g.addRaw(';');
|
|
680
|
+
}
|
|
681
|
+
function emitStatement(g, source, stmt, indent, isLast, opts) {
|
|
383
682
|
switch (stmt.type) {
|
|
384
683
|
case 'JSXElement':
|
|
385
|
-
if (getJSXTagName(stmt) === 'style')
|
|
386
|
-
|
|
387
|
-
|
|
684
|
+
if (getJSXTagName(stmt) === 'style') {
|
|
685
|
+
const openEnd = stmt.openingElement?.end ?? stmt.start;
|
|
686
|
+
const closeStart = stmt.closingElement?.start ?? stmt.end;
|
|
687
|
+
g.recordStyle(stmt.start, stmt.end, source.slice(openEnd, closeStart));
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
g.add(indent);
|
|
691
|
+
emitJSXElement(g, source, stmt, opts);
|
|
692
|
+
if (!isLast)
|
|
693
|
+
g.addRaw(';');
|
|
694
|
+
return;
|
|
388
695
|
case 'JSXFragment':
|
|
389
|
-
|
|
696
|
+
g.add(indent);
|
|
697
|
+
emitJSXFragment(g, source, stmt, opts);
|
|
698
|
+
if (!isLast)
|
|
699
|
+
g.addRaw(';');
|
|
700
|
+
return;
|
|
390
701
|
case 'JSXExpressionContainer': {
|
|
391
702
|
if (stmt.expression.type === 'JSXEmptyExpression')
|
|
392
|
-
return
|
|
393
|
-
|
|
394
|
-
|
|
703
|
+
return;
|
|
704
|
+
g.add(indent);
|
|
705
|
+
g.addRaw('{');
|
|
706
|
+
g.add(source.slice(stmt.expression.start, stmt.expression.end), stmt.expression.start, stmt.expression.end);
|
|
707
|
+
g.addRaw('}');
|
|
708
|
+
if (!isLast)
|
|
709
|
+
g.addRaw(';');
|
|
710
|
+
return;
|
|
395
711
|
}
|
|
396
712
|
case 'VeskBlock': {
|
|
397
713
|
if (stmt.tag === 'empty')
|
|
398
|
-
return
|
|
399
|
-
const lines = [];
|
|
714
|
+
return;
|
|
400
715
|
const innerStmts = stmt.body ?? [];
|
|
716
|
+
let firstLine = true;
|
|
401
717
|
for (let i = 0; i < innerStmts.length; i++) {
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
718
|
+
const before = g.code.length;
|
|
719
|
+
emitStatement(g, source, innerStmts[i], indent, isLast && i === innerStmts.length - 1, opts);
|
|
720
|
+
if (g.code.length > before) {
|
|
721
|
+
if (!firstLine)
|
|
722
|
+
g.addRaw('\n');
|
|
723
|
+
firstLine = false;
|
|
724
|
+
}
|
|
405
725
|
}
|
|
406
|
-
return
|
|
726
|
+
return;
|
|
407
727
|
}
|
|
728
|
+
case 'VariableDeclaration':
|
|
729
|
+
if (isTrackDeclStatement(stmt)) {
|
|
730
|
+
emitTrackDeclStatement(g, source, stmt, indent, isLast, opts);
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
return emitPlainStatement(g, source, stmt, indent, isLast);
|
|
408
734
|
case 'IfStatement':
|
|
409
|
-
return
|
|
735
|
+
return emitIf(g, source, stmt, indent, opts);
|
|
410
736
|
case 'ForOfStatement':
|
|
411
|
-
return
|
|
737
|
+
return emitForOf(g, source, stmt, indent, opts);
|
|
412
738
|
case 'ForInStatement':
|
|
413
|
-
return
|
|
739
|
+
return emitForIn(g, source, stmt, indent, opts);
|
|
414
740
|
case 'ForStatement':
|
|
415
|
-
return
|
|
741
|
+
return emitFor(g, source, stmt, indent, opts);
|
|
416
742
|
case 'WhileStatement':
|
|
417
|
-
return
|
|
743
|
+
return emitWhile(g, source, stmt, indent, opts);
|
|
418
744
|
case 'DoWhileStatement':
|
|
419
|
-
return
|
|
745
|
+
return emitDoWhile(g, source, stmt, indent, opts);
|
|
420
746
|
case 'SwitchStatement':
|
|
421
|
-
return
|
|
747
|
+
return emitSwitch(g, source, stmt, indent, opts);
|
|
422
748
|
case 'TryStatement':
|
|
423
|
-
return
|
|
749
|
+
return emitTry(g, source, stmt, indent, opts);
|
|
424
750
|
case 'LabeledStatement': {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
if (text === '')
|
|
431
|
-
return '';
|
|
432
|
-
return indent + text + (text.endsWith('}') || isLast ? '' : ';');
|
|
751
|
+
g.add(indent);
|
|
752
|
+
g.add(stmt.label.name, stmt.label.start, stmt.label.end);
|
|
753
|
+
g.addRaw(':\n');
|
|
754
|
+
emitStatement(g, source, stmt.body, indent, isLast, opts);
|
|
755
|
+
return;
|
|
433
756
|
}
|
|
757
|
+
default:
|
|
758
|
+
return emitPlainStatement(g, source, stmt, indent, isLast);
|
|
434
759
|
}
|
|
435
760
|
}
|
|
436
|
-
function
|
|
437
|
-
|
|
761
|
+
function emitBody(g, source, stmts, indent, opts) {
|
|
762
|
+
let firstLine = true;
|
|
438
763
|
let i = 0;
|
|
439
764
|
while (i < stmts.length) {
|
|
440
765
|
const stmt = stmts[i];
|
|
@@ -442,107 +767,254 @@ function tsxEmitBody(source, stmts, indent) {
|
|
|
442
767
|
if (stmt.type === 'ForOfStatement' &&
|
|
443
768
|
stmts[i + 1] && stmts[i + 1].type === 'VeskBlock' && stmts[i + 1].tag === 'empty') {
|
|
444
769
|
const empty = stmts[i + 1];
|
|
445
|
-
|
|
770
|
+
if (!firstLine)
|
|
771
|
+
g.addRaw('\n');
|
|
772
|
+
firstLine = false;
|
|
773
|
+
emitForOf(g, source, stmt, indent, opts);
|
|
446
774
|
const inner = empty.body ?? [];
|
|
447
775
|
if (inner.length > 0) {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
776
|
+
g.addRaw('\n');
|
|
777
|
+
g.add(indent);
|
|
778
|
+
g.add(source.slice(stmt.right.start, stmt.right.end), stmt.right.start, stmt.right.end);
|
|
779
|
+
g.addRaw('.length === 0 && (() => {');
|
|
780
|
+
g.addRaw('\n');
|
|
781
|
+
emitBody(g, source, inner, indent + ' ', opts);
|
|
782
|
+
g.addRaw('\n');
|
|
783
|
+
g.add(indent);
|
|
784
|
+
g.addRaw('})();');
|
|
453
785
|
}
|
|
454
786
|
i += 2;
|
|
455
787
|
continue;
|
|
456
788
|
}
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
789
|
+
const before = g.code.length;
|
|
790
|
+
emitStatement(g, source, stmt, indent, isLast, opts);
|
|
791
|
+
if (g.code.length > before) {
|
|
792
|
+
if (!firstLine)
|
|
793
|
+
g.addRaw('\n');
|
|
794
|
+
firstLine = false;
|
|
795
|
+
}
|
|
460
796
|
i++;
|
|
461
797
|
}
|
|
462
|
-
return lines.join('\n');
|
|
463
798
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
799
|
+
/**
|
|
800
|
+
* Emits `source[start, end)` as a mapped chunk, skipping any `<style>`
|
|
801
|
+
* blocks (recorded as style regions). Used for expression-mode bodies and
|
|
802
|
+
* the gaps around statements/expressions.
|
|
803
|
+
*/
|
|
804
|
+
function emitChunkMapped(g, source, start, end) {
|
|
805
|
+
if (end <= start)
|
|
806
|
+
return;
|
|
807
|
+
let pos = start;
|
|
808
|
+
let i = start;
|
|
809
|
+
while (i < end) {
|
|
810
|
+
if (source[i] === '<' && source.slice(i, i + 6) === '<style') {
|
|
811
|
+
const next = i + 6;
|
|
812
|
+
if (next < end && (isWhitespaceChar(source[next]) || source[next] === '>' || source[next] === '/')) {
|
|
813
|
+
const openEnd = htmlTagEnd(source, i);
|
|
814
|
+
if (openEnd !== -1 && openEnd <= end) {
|
|
815
|
+
const closeIdx = source.indexOf('</style>', openEnd);
|
|
816
|
+
if (closeIdx !== -1 && closeIdx + 8 <= end) {
|
|
817
|
+
g.add(source.slice(pos, i), pos, i);
|
|
818
|
+
g.recordStyle(i, closeIdx + 8, source.slice(openEnd, closeIdx));
|
|
819
|
+
pos = closeIdx + 8;
|
|
820
|
+
i = closeIdx + 8;
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
i++;
|
|
827
|
+
}
|
|
828
|
+
g.add(source.slice(pos, end), pos, end);
|
|
467
829
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
830
|
+
function emitReturn(g, source, stmt, opts) {
|
|
831
|
+
g.add(source.slice(stmt.start, stmt.start + 6), stmt.start, stmt.start + 6);
|
|
832
|
+
const arg = stmt.argument;
|
|
833
|
+
if (!arg) {
|
|
834
|
+
emitChunkMapped(g, source, stmt.start + 6, stmt.end);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
emitChunkMapped(g, source, stmt.start + 6, arg.start);
|
|
838
|
+
if (arg.type === 'JSXElement')
|
|
839
|
+
emitJSXElement(g, source, arg, opts);
|
|
840
|
+
else if (arg.type === 'JSXFragment')
|
|
841
|
+
emitJSXFragment(g, source, arg, opts);
|
|
842
|
+
else
|
|
843
|
+
emitChunkMapped(g, source, arg.start, arg.end);
|
|
844
|
+
emitChunkMapped(g, source, arg.end, stmt.end);
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Emits a statement-mode component body (bare JSX, control flow,
|
|
848
|
+
* guard-clause returns). The body text starts directly with the first
|
|
849
|
+
* statement (no leading whitespace) and the `{ ... }` wrapping is emitted
|
|
850
|
+
* by the caller.
|
|
851
|
+
*/
|
|
852
|
+
function emitBodyCore(g, source, stmts, opts) {
|
|
853
|
+
emitBody(g, source, stmts, '', opts);
|
|
854
|
+
}
|
|
855
|
+
/** Emits an expression-mode component body (`return <jsx>` or a bare expression). */
|
|
856
|
+
function emitExpressionBody(g, source, coreStart, coreEnd, stmts, opts) {
|
|
857
|
+
let pos = coreStart;
|
|
858
|
+
for (const stmt of stmts) {
|
|
859
|
+
if (!stmt)
|
|
479
860
|
continue;
|
|
861
|
+
emitChunkMapped(g, source, pos, stmt.start);
|
|
862
|
+
pos = stmt.start;
|
|
863
|
+
if (isTrackDeclStatement(stmt)) {
|
|
864
|
+
emitTrackDeclStatement(g, source, stmt, '', true, opts);
|
|
480
865
|
}
|
|
481
|
-
|
|
866
|
+
else if (stmt.type === 'ReturnStatement') {
|
|
867
|
+
emitReturn(g, source, stmt, opts);
|
|
868
|
+
}
|
|
869
|
+
else {
|
|
870
|
+
emitChunkMapped(g, source, stmt.start, stmt.end);
|
|
871
|
+
}
|
|
872
|
+
pos = stmt.end;
|
|
482
873
|
}
|
|
483
|
-
|
|
874
|
+
emitChunkMapped(g, source, pos, coreEnd);
|
|
484
875
|
}
|
|
485
876
|
/**
|
|
486
|
-
*
|
|
487
|
-
*
|
|
488
|
-
*
|
|
489
|
-
* replaced by a single space.
|
|
877
|
+
* Emits `text` (covering source range [base, end)) with its first
|
|
878
|
+
* identifier split into its own mapping so hover/rename/definition land
|
|
879
|
+
* exactly on the component name.
|
|
490
880
|
*/
|
|
491
|
-
function
|
|
492
|
-
let
|
|
493
|
-
while (
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
if (text.slice(wordStart, end) === 'client') {
|
|
499
|
-
return text.slice(0, wordStart).trimEnd() + ' ';
|
|
881
|
+
function emitRestMapped(g, text, base, end) {
|
|
882
|
+
let i = 0;
|
|
883
|
+
while (i < text.length && !isIdentStart(text[i]))
|
|
884
|
+
i++;
|
|
885
|
+
if (i >= text.length) {
|
|
886
|
+
g.add(text, base, end);
|
|
887
|
+
return;
|
|
500
888
|
}
|
|
501
|
-
|
|
889
|
+
let j = i + 1;
|
|
890
|
+
while (j < text.length && isIdentChar(text[j]))
|
|
891
|
+
j++;
|
|
892
|
+
g.add(text.slice(0, i), base, base + i);
|
|
893
|
+
g.add(text.slice(i, j), base + i, base + j);
|
|
894
|
+
g.add(text.slice(j), base + j, end);
|
|
502
895
|
}
|
|
503
|
-
|
|
896
|
+
function emitComponentHeader(g, source, start, bodyStart) {
|
|
897
|
+
const h = source.slice(start, bodyStart);
|
|
898
|
+
const k = findFirstKeyword(h, 'component');
|
|
899
|
+
if (k === -1) {
|
|
900
|
+
const { text, end } = stripTrailingClientRange(h);
|
|
901
|
+
if (!text.includes('(')) {
|
|
902
|
+
const trimmed = text.trimEnd();
|
|
903
|
+
g.add(trimmed, start, start + end - (text.length - trimmed.length));
|
|
904
|
+
g.addRaw('() ');
|
|
905
|
+
}
|
|
906
|
+
else {
|
|
907
|
+
g.add(text, start, start + end);
|
|
908
|
+
}
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
g.add(h.slice(0, k), start, start + k);
|
|
912
|
+
g.add('function', start + k, start + k + 'component'.length);
|
|
913
|
+
const restBase = start + k + 'component'.length;
|
|
914
|
+
const { text: rest, end } = stripTrailingClientRange(h.slice(k + 'component'.length));
|
|
915
|
+
const transformed = h.slice(0, k) + 'function' + rest;
|
|
916
|
+
if (!transformed.includes('(')) {
|
|
917
|
+
const trimmed = rest.trimEnd();
|
|
918
|
+
emitRestMapped(g, trimmed, restBase, restBase + end - (rest.length - trimmed.length));
|
|
919
|
+
g.addRaw('() ');
|
|
920
|
+
}
|
|
921
|
+
else {
|
|
922
|
+
emitRestMapped(g, rest, restBase, restBase + end);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
function emitComponent(g, source, node, start, opts) {
|
|
926
|
+
const body = node.body;
|
|
927
|
+
if (!body || body.start == null || body.end == null)
|
|
928
|
+
return;
|
|
929
|
+
emitComponentHeader(g, source, start, body.start);
|
|
930
|
+
const statementMode = isStatementMode(node);
|
|
931
|
+
const stmts = body.body ?? [];
|
|
932
|
+
if (statementMode) {
|
|
933
|
+
g.addRaw('{ ');
|
|
934
|
+
emitBodyCore(g, source, stmts, opts);
|
|
935
|
+
g.addRaw(' }');
|
|
936
|
+
}
|
|
937
|
+
else {
|
|
938
|
+
const raw = source.slice(body.start, body.end);
|
|
939
|
+
const rawLen = raw.length;
|
|
940
|
+
let s = 0;
|
|
941
|
+
let e = rawLen;
|
|
942
|
+
while (s < e && isWhitespaceChar(raw[s]))
|
|
943
|
+
s++;
|
|
944
|
+
while (e > s && isWhitespaceChar(raw[e - 1]))
|
|
945
|
+
e--;
|
|
946
|
+
const hasBraces = raw[s] === '{' && raw[e - 1] === '}';
|
|
947
|
+
let coreStart;
|
|
948
|
+
let coreEnd;
|
|
949
|
+
if (hasBraces) {
|
|
950
|
+
coreStart = body.start + s + 1;
|
|
951
|
+
coreEnd = body.start + e - 1;
|
|
952
|
+
}
|
|
953
|
+
else {
|
|
954
|
+
coreStart = body.start + s;
|
|
955
|
+
coreEnd = body.start + e;
|
|
956
|
+
}
|
|
957
|
+
while (coreStart < coreEnd && isWhitespaceChar(source[coreStart]))
|
|
958
|
+
coreStart++;
|
|
959
|
+
while (coreEnd > coreStart && isWhitespaceChar(source[coreEnd - 1]))
|
|
960
|
+
coreEnd--;
|
|
961
|
+
if (hasBraces)
|
|
962
|
+
g.addRaw('{ ');
|
|
963
|
+
emitExpressionBody(g, source, coreStart, coreEnd, stmts, opts);
|
|
964
|
+
if (hasBraces)
|
|
965
|
+
g.addRaw(' }');
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
function isModuleAst(ast) {
|
|
969
|
+
return (ast.body || []).some((s) => s.type === 'ImportDeclaration' ||
|
|
970
|
+
s.type === 'ExportNamedDeclaration' ||
|
|
971
|
+
s.type === 'ExportDefaultDeclaration' ||
|
|
972
|
+
s.type === 'ExportAllDeclaration');
|
|
973
|
+
}
|
|
974
|
+
export function compileVskCodegen(source, opts = {}) {
|
|
504
975
|
let ast;
|
|
505
976
|
try {
|
|
506
977
|
ast = parse(source);
|
|
507
978
|
}
|
|
508
|
-
catch {
|
|
509
|
-
|
|
979
|
+
catch (e) {
|
|
980
|
+
const pos = e.pos ?? 0;
|
|
981
|
+
return {
|
|
982
|
+
code: source,
|
|
983
|
+
mappings: [
|
|
984
|
+
{
|
|
985
|
+
sourceOffsets: [0],
|
|
986
|
+
generatedOffsets: [0],
|
|
987
|
+
lengths: [source.length],
|
|
988
|
+
generatedLengths: [source.length],
|
|
989
|
+
data: FULL_DATA,
|
|
990
|
+
},
|
|
991
|
+
],
|
|
992
|
+
styleRegions: [],
|
|
993
|
+
errors: [{ message: e.message, start: pos, end: pos + 1 }],
|
|
994
|
+
};
|
|
510
995
|
}
|
|
511
996
|
const clean = blankForClauses(source, ast);
|
|
512
|
-
const
|
|
513
|
-
|
|
997
|
+
const g = new VskGen();
|
|
998
|
+
const components = getComponents(ast).sort((a, b) => a.start - b.start);
|
|
999
|
+
let pos = 0;
|
|
1000
|
+
for (const { node, start } of components) {
|
|
514
1001
|
const body = node.body;
|
|
515
1002
|
if (!body || body.start == null || body.end == null)
|
|
516
1003
|
continue;
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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 });
|
|
1004
|
+
g.add(clean.slice(pos, start), pos, start);
|
|
1005
|
+
emitComponent(g, clean, node, start, opts);
|
|
1006
|
+
pos = body.end;
|
|
536
1007
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
if (isModule && containsIdentifier(out, 'Head') && !isIdentifierImported(out, 'Head')) {
|
|
543
|
-
return `declare const Head: (props: { children?: unknown }) => unknown;\n${out}`;
|
|
1008
|
+
g.add(clean.slice(pos), pos, source.length);
|
|
1009
|
+
let code = g.code;
|
|
1010
|
+
if (isModuleAst(ast) && containsIdentifier(code, 'Head') && !isIdentifierImported(code, 'Head')) {
|
|
1011
|
+
g.prepend('declare const Head: (props: { children?: unknown }) => unknown;\n');
|
|
1012
|
+
code = g.code;
|
|
544
1013
|
}
|
|
545
|
-
return
|
|
1014
|
+
return { code, mappings: g.mappings, styleRegions: g.styleRegions, errors: [] };
|
|
1015
|
+
}
|
|
1016
|
+
export function vskToTsx(source) {
|
|
1017
|
+
return compileVskCodegen(source).code;
|
|
546
1018
|
}
|
|
547
1019
|
function isTypeDecl(stmt) {
|
|
548
1020
|
return (stmt.type === 'TSInterfaceDeclaration' ||
|