@tsrx/core 0.1.19 → 0.1.22

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.
@@ -4,11 +4,12 @@
4
4
  * @param {Iterable<T> | Iterator<T>} iterable
5
5
  * @param {(item: T, index: number, is_last: boolean) => U} fn
6
6
  * @param {() => U | U[]} [tail]
7
+ * @param {() => U | U[]} [empty]
7
8
  * @returns {U[]}
8
9
  */
9
- export function map_iterable(iterable, fn, tail) {
10
+ export function map_iterable(iterable, fn, tail, empty) {
10
11
  if (Array.isArray(iterable)) {
11
- return map_array(iterable, fn, tail);
12
+ return map_array(iterable, fn, tail, empty);
12
13
  }
13
14
 
14
15
  /** @type {Iterator<T>} */
@@ -25,14 +26,14 @@ export function map_iterable(iterable, fn, tail) {
25
26
 
26
27
  var current = iterator.next();
27
28
  if (current.done) {
28
- if (!tail) {
29
+ if (!empty) {
29
30
  return [];
30
31
  }
31
- var tail_value = tail();
32
- if (Array.isArray(tail_value)) {
33
- return tail_value;
32
+ var empty_value = empty();
33
+ if (Array.isArray(empty_value)) {
34
+ return empty_value;
34
35
  }
35
- return [tail_value];
36
+ return [empty_value];
36
37
  }
37
38
 
38
39
  var index = 0;
@@ -71,19 +72,20 @@ export function map_iterable(iterable, fn, tail) {
71
72
  * @param {Array<T>} array
72
73
  * @param {(item: T, index: number, is_last: boolean) => U} fn
73
74
  * @param {() => U | U[]} [tail]
75
+ * @param {() => U | U[]} [empty]
74
76
  * @returns {U[]}
75
77
  */
76
- function map_array(array, fn, tail) {
78
+ function map_array(array, fn, tail, empty) {
77
79
  var length = array.length;
78
80
  if (length === 0) {
79
- if (!tail) {
81
+ if (!empty) {
80
82
  return [];
81
83
  }
82
- var tail_value = tail();
83
- if (Array.isArray(tail_value)) {
84
- return tail_value;
84
+ var empty_value = empty();
85
+ if (Array.isArray(empty_value)) {
86
+ return empty_value;
85
87
  }
86
- return [tail_value];
88
+ return [empty_value];
87
89
  }
88
90
  var result = [];
89
91
  for (var i = 0; i < length; i++) {
package/src/scope.js CHANGED
@@ -107,18 +107,28 @@ export function create_scopes(ast, root, parent, error_options) {
107
107
  }
108
108
  },
109
109
 
110
- Element(node, { state, next }) {
111
- const scope = state.scope.child();
112
- scopes.set(node, scope);
110
+ JSXElement(node, { state, next }) {
111
+ if (node.metadata?.native_tsrx) {
112
+ const scope = state.scope.child();
113
+ scopes.set(node, scope);
113
114
 
114
- next({ scope });
115
+ next({ scope });
116
+ return;
117
+ }
118
+
119
+ next();
115
120
  },
116
121
 
117
- Tsrx(node, { state, next }) {
118
- const scope = state.scope.child();
119
- scopes.set(node, scope);
122
+ JSXFragment(node, { state, next }) {
123
+ if (node.metadata?.native_tsrx) {
124
+ const scope = state.scope.child();
125
+ scopes.set(node, scope);
120
126
 
121
- next({ scope });
127
+ next({ scope });
128
+ return;
129
+ }
130
+
131
+ next();
122
132
  },
123
133
 
124
134
  TSModuleDeclaration(node, { state, next }) {
@@ -169,6 +179,8 @@ export function create_scopes(ast, root, parent, error_options) {
169
179
  ForInStatement: create_block_scope,
170
180
  ForOfStatement: create_block_scope,
171
181
  SwitchStatement: create_block_scope,
182
+ JSXForExpression: create_block_scope,
183
+ JSXSwitchExpression: create_block_scope,
172
184
  BlockStatement(node, context) {
173
185
  const parent = context.path.at(-1);
174
186
  if (
@@ -192,13 +204,16 @@ export function create_scopes(ast, root, parent, error_options) {
192
204
  for (const declarator of node.declarations) {
193
205
  /** @type {Binding[]} */
194
206
  const bindings = [];
195
- const initial = /** @type {AST.Expression | AST.Tsx | AST.Tsrx | null} */ (declarator.init);
207
+ const initial = /** @type {AST.Expression | null} */ (declarator.init);
196
208
 
197
209
  state.scope.declarators.set(declarator, bindings);
198
210
 
199
211
  for (const id of extract_identifiers(declarator.id)) {
200
212
  const binding = state.scope.declare(id, 'normal', node.kind, initial);
201
- if (initial?.type === 'Tsx' || initial?.type === 'Tsrx') {
213
+ if (
214
+ (initial?.type === 'JSXElement' || initial?.type === 'JSXFragment') &&
215
+ initial.metadata?.native_tsrx
216
+ ) {
202
217
  binding.metadata = {
203
218
  ...(binding.metadata ?? {}),
204
219
  is_template_value: true,
@@ -14,7 +14,7 @@
14
14
  * end_column: number,
15
15
  * code: string,
16
16
  * metadata: {
17
- * css?: AST.Element['metadata']['css']
17
+ * css?: AST.Node['metadata']['css']
18
18
  * },
19
19
  * }} CodePosition
20
20
  * @typedef {{
@@ -42,7 +42,11 @@ export function find_first_top_level_await(node, inside_nested_function) {
42
42
  return null;
43
43
  }
44
44
 
45
- if (node.type === 'AwaitExpression' || (node.type === 'ForOfStatement' && node.await === true)) {
45
+ if (
46
+ node.type === 'AwaitExpression' ||
47
+ (node.type === 'ForOfStatement' && node.await === true) ||
48
+ (node.type === 'JSXForExpression' && node.await === true)
49
+ ) {
46
50
  return node;
47
51
  }
48
52
 
@@ -89,6 +89,44 @@ export function clone_jsx_name(name, source_node = name) {
89
89
  return name;
90
90
  }
91
91
 
92
+ /**
93
+ * Convert a JSX tag name back into a JavaScript expression. Dynamic element
94
+ * tags are parsed as JSX-shaped names, but the runtime alias needs ordinary JS.
95
+ *
96
+ * @param {any} name
97
+ * @returns {any}
98
+ */
99
+ export function jsx_name_to_expression(name) {
100
+ if (!name) return name;
101
+ if (name.type === 'JSXIdentifier') {
102
+ return set_loc(
103
+ /** @type {any} */ ({
104
+ type: 'Identifier',
105
+ name: name.name,
106
+ metadata: name.metadata || { path: [] },
107
+ }),
108
+ name,
109
+ );
110
+ }
111
+ if (name.type === 'JSXMemberExpression') {
112
+ return set_loc(
113
+ /** @type {any} */ ({
114
+ type: 'MemberExpression',
115
+ object: jsx_name_to_expression(name.object),
116
+ property: jsx_name_to_expression(name.property),
117
+ computed: false,
118
+ optional: false,
119
+ metadata: name.metadata || { path: [] },
120
+ }),
121
+ name,
122
+ );
123
+ }
124
+ if (name.type === 'Identifier' || name.type === 'MemberExpression') {
125
+ return clone_expression_node(name);
126
+ }
127
+ return name;
128
+ }
129
+
92
130
  /**
93
131
  * @returns {AST.Literal}
94
132
  */
@@ -242,12 +280,10 @@ export function is_jsx_child(node) {
242
280
  t === 'JSXFragment' ||
243
281
  t === 'JSXExpressionContainer' ||
244
282
  t === 'JSXText' ||
245
- t === 'Tsx' ||
246
- t === 'Tsrx' ||
247
- t === 'TsxCompat' ||
248
- t === 'Element' ||
249
- t === 'Text' ||
250
- t === 'TSRXExpression' ||
283
+ t === 'JSXIfExpression' ||
284
+ t === 'JSXForExpression' ||
285
+ t === 'JSXSwitchExpression' ||
286
+ t === 'JSXTryExpression' ||
251
287
  t === 'IfStatement' ||
252
288
  t === 'ForOfStatement' ||
253
289
  t === 'SwitchStatement' ||
@@ -256,8 +292,8 @@ export function is_jsx_child(node) {
256
292
  }
257
293
 
258
294
  /**
259
- * The parser represents `<>{expr}</>` / `<tsx>{expr}</tsx>` as a Tsx node,
260
- * and expression-position lowering unwraps that to the inner expression.
295
+ * Expression-position lowering unwraps single-expression native fragments to
296
+ * the inner expression.
261
297
  * When such a node appears directly in a component or statement render body,
262
298
  * the unwrapped expression is still render output rather than an executable
263
299
  * statement.
@@ -317,10 +353,10 @@ export function is_dynamic_element_id(id) {
317
353
  if (!id || typeof id !== 'object') {
318
354
  return false;
319
355
  }
320
- if (id.type === 'Identifier') {
356
+ if (id.type === 'Identifier' || id.type === 'JSXIdentifier') {
321
357
  return !!id.tracked;
322
358
  }
323
- if (id.type === 'MemberExpression') {
359
+ if (id.type === 'MemberExpression' || id.type === 'JSXMemberExpression') {
324
360
  return is_dynamic_element_id(id.object);
325
361
  }
326
362
  return false;
@@ -369,58 +405,6 @@ export function flatten_switch_consequent(consequent) {
369
405
  return result;
370
406
  }
371
407
 
372
- /**
373
- * Compute fall-through expansions for each `case` in a `switch`. JavaScript
374
- * `switch` semantics say that once a case body executes, execution continues
375
- * into the bodies of subsequent cases until a `break` or terminal `return` is
376
- * hit. We pre-compute, per case, the flat list of statements that should run
377
- * when that case is the entry point — so downstream targets (which render each
378
- * case independently rather than executing fall-through at runtime) still
379
- * produce the right output.
380
- *
381
- * Walking right-to-left lets each case reuse the next case's already-expanded
382
- * tail without recomputation. Downstream nodes are deep-cloned when absorbed
383
- * so each case's expanded body owns its own AST subtree.
384
- *
385
- * @param {any[]} cases
386
- * @returns {Array<{ test: any, body: any[], source: any }>}
387
- */
388
- export function expand_switch_cases_for_fallthrough(cases) {
389
- /** @type {Array<{ test: any, body: any[], source: any }>} */
390
- const expanded = new Array(cases.length);
391
- for (let i = cases.length - 1; i >= 0; i--) {
392
- const consequent = flatten_switch_consequent(cases[i].consequent || []);
393
- const body = [];
394
- let has_terminal = false;
395
- for (const child of consequent) {
396
- if (child.type === 'BreakStatement') {
397
- has_terminal = true;
398
- break;
399
- }
400
- body.push(child);
401
- if (child.type === 'ReturnStatement') {
402
- has_terminal = true;
403
- break;
404
- }
405
- }
406
- // Strip locations from cloned downstream nodes. Only the original case
407
- // (one entry up the chain) keeps `loc`/`start`/`end`; clones inlined
408
- // into upstream cases would otherwise point editor IntelliSense at the
409
- // same source range multiple times (one hover/go-to-definition per
410
- // fall-through entry point), producing double/triple results in Volar.
411
- const downstream =
412
- !has_terminal && i + 1 < cases.length
413
- ? expanded[i + 1].body.map((n) => clone_expression_node(n, false))
414
- : [];
415
- expanded[i] = {
416
- test: cases[i].test,
417
- body: [...body, ...downstream],
418
- source: cases[i],
419
- };
420
- }
421
- return expanded;
422
- }
423
-
424
408
  /**
425
409
  * @param {AST.Expression | null | undefined} expression
426
410
  * @returns {boolean}
@@ -4,9 +4,8 @@
4
4
  import tsx from 'esrap/languages/tsx';
5
5
 
6
6
  /**
7
- * Zimmerframe provides `path` as the ancestor chain (in original pre-transform
8
- * types, since visitors run bottom-up). A Tsx node whose parent is a ripple
9
- * `Element` will render as a JSX child of that element; anywhere else it
7
+ * Zimmerframe provides `path` as the ancestor chain. A native template node whose
8
+ * parent is another native template node renders as a JSX child; anywhere else it
10
9
  * renders as a standalone expression (e.g. a return value).
11
10
  *
12
11
  * @param {any[]} path
@@ -14,7 +13,11 @@ import tsx from 'esrap/languages/tsx';
14
13
  */
15
14
  export function in_jsx_child_context(path) {
16
15
  const parent = path[path.length - 1];
17
- return !!parent && parent.type === 'Element';
16
+ return (
17
+ !!parent &&
18
+ (parent.type === 'JSXElement' || parent.type === 'JSXFragment') &&
19
+ parent.metadata?.native_tsrx
20
+ );
18
21
  }
19
22
 
20
23
  /**
@@ -35,7 +38,7 @@ export function set_node_path_metadata(node, path) {
35
38
  }
36
39
 
37
40
  /**
38
- * Flatten a `<tsx>` / fragment node's children into a single expression. In a
41
+ * Flatten a JSX-compatible island's children into a single expression. In a
39
42
  * JSX-child position, a JSXExpressionContainer `{expr}` is valid and must stay
40
43
  * wrapped. In an expression position (e.g. `return ...`), `{expr}` parses as
41
44
  * a block/object literal, so unwrap to `expr`.