@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.
@@ -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
+ }
@@ -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
- * @returns {[CodeToGeneratedMap, GeneratedToSourceMap]} Tuple of [source-to-generated map, generated-to-source map]
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}