@tsrx/core 0.1.3 → 0.1.6
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/package.json +23 -5
- package/src/diagnostics.js +1 -0
- package/src/index.js +7 -0
- package/src/plugin.js +294 -230
- package/src/runtime/index.js +110 -0
- package/src/source-map-utils.js +19 -2
- package/src/transform/jsx/ast-builders.js +120 -0
- package/src/transform/jsx/index.js +1575 -1440
- package/src/transform/lazy.js +19 -60
- package/src/transform/scoping.js +9 -45
- package/src/transform/segments.js +164 -11
- package/src/utils/builders.js +51 -13
- package/types/jsx-platform.d.ts +10 -0
- package/types/runtime/index.d.ts +13 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @template T
|
|
3
|
+
* @template U
|
|
4
|
+
* @param {Iterable<T> | Iterator<T>} iterable
|
|
5
|
+
* @param {(item: T, index: number, is_last: boolean) => U} fn
|
|
6
|
+
* @param {() => U | U[]} [tail]
|
|
7
|
+
* @returns {U[]}
|
|
8
|
+
*/
|
|
9
|
+
export function map_iterable(iterable, fn, tail) {
|
|
10
|
+
if (Array.isArray(iterable)) {
|
|
11
|
+
return map_array(iterable, fn, tail);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** @type {Iterator<T>} */
|
|
15
|
+
var iterator;
|
|
16
|
+
var iterable_prop = /** @type {Iterable<T>} */ (iterable)[Symbol.iterator];
|
|
17
|
+
|
|
18
|
+
if (typeof iterable_prop === 'function') {
|
|
19
|
+
iterator = iterable_prop.call(iterable);
|
|
20
|
+
} else if (typeof (/** @type {Iterator<T>} */ (iterable).next) === 'function') {
|
|
21
|
+
iterator = Iterator.from(iterable);
|
|
22
|
+
} else {
|
|
23
|
+
throw new TypeError('The loop target has to be an Iterable');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
var current = iterator.next();
|
|
27
|
+
if (current.done) {
|
|
28
|
+
if (!tail) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
var tail_value = tail();
|
|
32
|
+
if (Array.isArray(tail_value)) {
|
|
33
|
+
return tail_value;
|
|
34
|
+
}
|
|
35
|
+
return [tail_value];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
var index = 0;
|
|
39
|
+
var result = [];
|
|
40
|
+
while (true) {
|
|
41
|
+
var next = iterator.next();
|
|
42
|
+
var value = fn(current.value, index++, !!next.done);
|
|
43
|
+
if (Array.isArray(value)) {
|
|
44
|
+
for (var j = 0; j < value.length; j++) {
|
|
45
|
+
result.push(value[j]);
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
result.push(value);
|
|
49
|
+
}
|
|
50
|
+
if (next.done) {
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
current = next;
|
|
54
|
+
}
|
|
55
|
+
if (tail) {
|
|
56
|
+
var tail_value = tail();
|
|
57
|
+
if (Array.isArray(tail_value)) {
|
|
58
|
+
for (var j = 0; j < tail_value.length; j++) {
|
|
59
|
+
result.push(tail_value[j]);
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
result.push(tail_value);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @template T
|
|
70
|
+
* @template U
|
|
71
|
+
* @param {Array<T>} array
|
|
72
|
+
* @param {(item: T, index: number, is_last: boolean) => U} fn
|
|
73
|
+
* @param {() => U | U[]} [tail]
|
|
74
|
+
* @returns {U[]}
|
|
75
|
+
*/
|
|
76
|
+
function map_array(array, fn, tail) {
|
|
77
|
+
var length = array.length;
|
|
78
|
+
if (length === 0) {
|
|
79
|
+
if (!tail) {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
var tail_value = tail();
|
|
83
|
+
if (Array.isArray(tail_value)) {
|
|
84
|
+
return tail_value;
|
|
85
|
+
}
|
|
86
|
+
return [tail_value];
|
|
87
|
+
}
|
|
88
|
+
var result = [];
|
|
89
|
+
for (var i = 0; i < length; i++) {
|
|
90
|
+
var value = fn(array[i], i, i === length - 1);
|
|
91
|
+
if (Array.isArray(value)) {
|
|
92
|
+
for (var j = 0; j < value.length; j++) {
|
|
93
|
+
result.push(value[j]);
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
result.push(value);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (tail) {
|
|
100
|
+
var tail_value = tail();
|
|
101
|
+
if (Array.isArray(tail_value)) {
|
|
102
|
+
for (var j = 0; j < tail_value.length; j++) {
|
|
103
|
+
result.push(tail_value[j]);
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
result.push(tail_value);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
}
|
package/src/source-map-utils.js
CHANGED
|
@@ -17,10 +17,15 @@
|
|
|
17
17
|
* css?: AST.Element['metadata']['css']
|
|
18
18
|
* },
|
|
19
19
|
* }} CodePosition
|
|
20
|
+
* @typedef {{
|
|
21
|
+
* column: number,
|
|
22
|
+
* position: CodePosition,
|
|
23
|
+
* }} SourceLineGeneratedPosition
|
|
20
24
|
*/
|
|
21
25
|
|
|
22
26
|
/** @typedef {Map<string, CodePosition[]>} CodeToGeneratedMap */
|
|
23
27
|
/** @typedef {Map<string, {line: number, column: number}[]>} GeneratedToSourceMap */
|
|
28
|
+
/** @typedef {Map<number, SourceLineGeneratedPosition[]>} SourceLineGeneratedMap */
|
|
24
29
|
|
|
25
30
|
import { decode } from '@jridgewell/sourcemap-codec';
|
|
26
31
|
|
|
@@ -83,18 +88,22 @@ export const offset_to_line_col = (offset, line_offsets) => {
|
|
|
83
88
|
* @param {PostProcessingChanges} post_processing_changes - Optional post-processing changes to apply
|
|
84
89
|
* @param {LineOffsets} line_offsets - Pre-computed line offsets array
|
|
85
90
|
* @param {string} generated_code - The final generated code (after post-processing)
|
|
86
|
-
* @
|
|
91
|
+
* @param {boolean} [include_source_line_generated_map] - Whether to build the optional source-line predecessor lookup
|
|
92
|
+
* @returns {[CodeToGeneratedMap, GeneratedToSourceMap, SourceLineGeneratedMap | null]} Tuple of [source-to-generated map, generated-to-source map, source-line generated map]
|
|
87
93
|
*/
|
|
88
94
|
export function build_src_to_gen_map(
|
|
89
95
|
source_map,
|
|
90
96
|
post_processing_changes,
|
|
91
97
|
line_offsets,
|
|
92
98
|
generated_code,
|
|
99
|
+
include_source_line_generated_map = false,
|
|
93
100
|
) {
|
|
94
101
|
/** @type {CodeToGeneratedMap} */
|
|
95
102
|
const map = new Map();
|
|
96
103
|
/** @type {GeneratedToSourceMap} */
|
|
97
104
|
const reverse_map = new Map();
|
|
105
|
+
/** @type {SourceLineGeneratedMap | null} */
|
|
106
|
+
const source_line_generated_map = include_source_line_generated_map ? new Map() : null;
|
|
98
107
|
|
|
99
108
|
// Decode the VLQ-encoded mappings string
|
|
100
109
|
const decoded = decode(source_map.mappings);
|
|
@@ -192,6 +201,14 @@ export function build_src_to_gen_map(
|
|
|
192
201
|
map.set(key, []);
|
|
193
202
|
}
|
|
194
203
|
/** @type {CodePosition[]} */ (map.get(key)).push(gen_pos);
|
|
204
|
+
if (source_line_generated_map) {
|
|
205
|
+
if (!source_line_generated_map.has(segment.sourceLine)) {
|
|
206
|
+
source_line_generated_map.set(segment.sourceLine, []);
|
|
207
|
+
}
|
|
208
|
+
/** @type {SourceLineGeneratedPosition[]} */ (
|
|
209
|
+
source_line_generated_map.get(segment.sourceLine)
|
|
210
|
+
).push({ column: segment.sourceColumn, position: gen_pos });
|
|
211
|
+
}
|
|
195
212
|
|
|
196
213
|
// Store reverse mapping (generated to source)
|
|
197
214
|
const gen_key = `${gen_pos.line}:${gen_pos.column}`;
|
|
@@ -206,7 +223,7 @@ export function build_src_to_gen_map(
|
|
|
206
223
|
}
|
|
207
224
|
}
|
|
208
225
|
|
|
209
|
-
return [map, reverse_map];
|
|
226
|
+
return [map, reverse_map, source_line_generated_map];
|
|
210
227
|
}
|
|
211
228
|
|
|
212
229
|
/**
|
|
@@ -162,6 +162,74 @@ export function identifier_to_jsx_name(id) {
|
|
|
162
162
|
return id;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
/**
|
|
166
|
+
* A JSX tag name refers to a *component* (rather than a host/DOM tag) iff:
|
|
167
|
+
* - it's a `JSXIdentifier` whose first character is uppercase (the convention
|
|
168
|
+
* every framework's JSX runtime keys off — `<div>` is a host element,
|
|
169
|
+
* `<Foo>` is a component), or
|
|
170
|
+
* - it's a `JSXMemberExpression` (e.g. `<Icons.Button />`).
|
|
171
|
+
*
|
|
172
|
+
* Used by platforms that veto static-hoisting of component JSX (Vue, Solid)
|
|
173
|
+
* and by core's narrower bare-component-invocation predicate.
|
|
174
|
+
*
|
|
175
|
+
* @param {any} name
|
|
176
|
+
* @returns {boolean}
|
|
177
|
+
*/
|
|
178
|
+
export function is_component_jsx_name(name) {
|
|
179
|
+
if (!name || typeof name !== 'object') {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (name.type === 'JSXIdentifier') {
|
|
184
|
+
const first = name.name?.[0];
|
|
185
|
+
return first != null && first >= 'A' && first <= 'Z';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (name.type === 'JSXMemberExpression') {
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Does this JSX subtree contain any component-shaped element (anywhere —
|
|
197
|
+
* including nested under host elements or inside expression containers)?
|
|
198
|
+
* Vue and Solid use this as their `canHoistStaticNode` predicate: hoisting a
|
|
199
|
+
* subtree that invokes a component into a module-level constant pins that
|
|
200
|
+
* component instance to module identity, which doesn't help either framework
|
|
201
|
+
* the way it helps React, so it's wasted output.
|
|
202
|
+
*
|
|
203
|
+
* @param {any} node
|
|
204
|
+
* @returns {boolean}
|
|
205
|
+
*/
|
|
206
|
+
export function contains_component_jsx(node) {
|
|
207
|
+
if (!node || typeof node !== 'object') {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (node.type === 'JSXElement') {
|
|
212
|
+
if (is_component_jsx_name(node.openingElement?.name)) {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
return node.children?.some(contains_component_jsx) ?? false;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (node.type === 'JSXFragment') {
|
|
219
|
+
return node.children?.some(contains_component_jsx) ?? false;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (node.type === 'JSXExpressionContainer') {
|
|
223
|
+
return contains_component_jsx(node.expression);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (Array.isArray(node)) {
|
|
227
|
+
return node.some(contains_component_jsx);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
|
|
165
233
|
/**
|
|
166
234
|
* @param {any} node
|
|
167
235
|
* @returns {boolean}
|
|
@@ -302,6 +370,58 @@ export function flatten_switch_consequent(consequent) {
|
|
|
302
370
|
return result;
|
|
303
371
|
}
|
|
304
372
|
|
|
373
|
+
/**
|
|
374
|
+
* Compute fall-through expansions for each `case` in a `switch`. JavaScript
|
|
375
|
+
* `switch` semantics say that once a case body executes, execution continues
|
|
376
|
+
* into the bodies of subsequent cases until a `break` or terminal `return` is
|
|
377
|
+
* hit. We pre-compute, per case, the flat list of statements that should run
|
|
378
|
+
* when that case is the entry point — so downstream targets (which render each
|
|
379
|
+
* case independently rather than executing fall-through at runtime) still
|
|
380
|
+
* produce the right output.
|
|
381
|
+
*
|
|
382
|
+
* Walking right-to-left lets each case reuse the next case's already-expanded
|
|
383
|
+
* tail without recomputation. Downstream nodes are deep-cloned when absorbed
|
|
384
|
+
* so each case's expanded body owns its own AST subtree.
|
|
385
|
+
*
|
|
386
|
+
* @param {any[]} cases
|
|
387
|
+
* @returns {Array<{ test: any, body: any[], source: any }>}
|
|
388
|
+
*/
|
|
389
|
+
export function expand_switch_cases_for_fallthrough(cases) {
|
|
390
|
+
/** @type {Array<{ test: any, body: any[], source: any }>} */
|
|
391
|
+
const expanded = new Array(cases.length);
|
|
392
|
+
for (let i = cases.length - 1; i >= 0; i--) {
|
|
393
|
+
const consequent = flatten_switch_consequent(cases[i].consequent || []);
|
|
394
|
+
const body = [];
|
|
395
|
+
let has_terminal = false;
|
|
396
|
+
for (const child of consequent) {
|
|
397
|
+
if (child.type === 'BreakStatement') {
|
|
398
|
+
has_terminal = true;
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
body.push(child);
|
|
402
|
+
if (child.type === 'ReturnStatement') {
|
|
403
|
+
has_terminal = true;
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
// Strip locations from cloned downstream nodes. Only the original case
|
|
408
|
+
// (one entry up the chain) keeps `loc`/`start`/`end`; clones inlined
|
|
409
|
+
// into upstream cases would otherwise point editor IntelliSense at the
|
|
410
|
+
// same source range multiple times (one hover/go-to-definition per
|
|
411
|
+
// fall-through entry point), producing double/triple results in Volar.
|
|
412
|
+
const downstream =
|
|
413
|
+
!has_terminal && i + 1 < cases.length
|
|
414
|
+
? expanded[i + 1].body.map((n) => clone_expression_node(n, false))
|
|
415
|
+
: [];
|
|
416
|
+
expanded[i] = {
|
|
417
|
+
test: cases[i].test,
|
|
418
|
+
body: [...body, ...downstream],
|
|
419
|
+
source: cases[i],
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
return expanded;
|
|
423
|
+
}
|
|
424
|
+
|
|
305
425
|
/**
|
|
306
426
|
* @param {AST.Expression | null | undefined} expression
|
|
307
427
|
* @returns {boolean}
|