@tsrx/core 0.1.20 → 0.1.24

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.
@@ -1,3 +1,3 @@
1
- export { is_boolean_attribute } from '../utils/dom.js';
1
+ export { is_boolean_attribute, is_void_element } from '../utils/dom.js';
2
2
  export { escape, escape_script } from '../utils/escaping.js';
3
3
  export { normalize_css_property_name } from '../utils/normalize_css_property_name.js';
@@ -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++) {
@@ -89,3 +89,42 @@ export function iterable_array_from(iterable, index = 0) {
89
89
  }
90
90
  return result;
91
91
  }
92
+
93
+ /**
94
+ * Creates a shallow forwarding object without one prop. Values are exposed through
95
+ * getters so compiler-emitted reactive prop accessors are not snapshotted.
96
+ * @param {Record<PropertyKey, any> | null | undefined} props
97
+ * @param {PropertyKey} exclude_prop
98
+ * @returns {Record<PropertyKey, any>}
99
+ */
100
+ export function exclude_prop_from_object(props, exclude_prop) {
101
+ /** @type {Record<PropertyKey, any>} */
102
+ const next = {};
103
+ if (props == null) return next;
104
+
105
+ for (const prop of Reflect.ownKeys(props)) {
106
+ if (prop === exclude_prop) continue;
107
+
108
+ const descriptor = get_descriptor(props, prop);
109
+ if (!descriptor?.enumerable) continue;
110
+
111
+ /** @type {PropertyDescriptor} */
112
+ const forwarding_descriptor = {
113
+ enumerable: true,
114
+ configurable: true,
115
+ get() {
116
+ return props[prop];
117
+ },
118
+ };
119
+
120
+ if (descriptor.writable === true || typeof descriptor.set === 'function') {
121
+ forwarding_descriptor.set = (value) => {
122
+ props[prop] = value;
123
+ };
124
+ }
125
+
126
+ define_property(next, prop, forwarding_descriptor);
127
+ }
128
+
129
+ return next;
130
+ }
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
- TsrxFragment(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.TsrxFragment | 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 === 'TsrxFragment') {
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
 
@@ -242,11 +242,10 @@ export function is_jsx_child(node) {
242
242
  t === 'JSXFragment' ||
243
243
  t === 'JSXExpressionContainer' ||
244
244
  t === 'JSXText' ||
245
- t === 'TsrxFragment' ||
246
- t === 'TsxCompat' ||
247
- t === 'Element' ||
248
- t === 'Text' ||
249
- t === 'TSRXExpression' ||
245
+ t === 'JSXIfExpression' ||
246
+ t === 'JSXForExpression' ||
247
+ t === 'JSXSwitchExpression' ||
248
+ t === 'JSXTryExpression' ||
250
249
  t === 'IfStatement' ||
251
250
  t === 'ForOfStatement' ||
252
251
  t === 'SwitchStatement' ||
@@ -305,26 +304,6 @@ export function is_bare_render_expression(node) {
305
304
  }
306
305
  }
307
306
 
308
- /**
309
- * A dynamic element id is one whose identifier is `tracked` — i.e. it was
310
- * introduced by reactive destructuring so its value can change at runtime.
311
- *
312
- * @param {any} id
313
- * @returns {boolean}
314
- */
315
- export function is_dynamic_element_id(id) {
316
- if (!id || typeof id !== 'object') {
317
- return false;
318
- }
319
- if (id.type === 'Identifier') {
320
- return !!id.tracked;
321
- }
322
- if (id.type === 'MemberExpression') {
323
- return is_dynamic_element_id(id.object);
324
- }
325
- return false;
326
- }
327
-
328
307
  /**
329
308
  * Gather the params a `for (x of y; index i)` loop should expose to its body
330
309
  * JSX (value first, optional index second).
@@ -368,58 +347,6 @@ export function flatten_switch_consequent(consequent) {
368
347
  return result;
369
348
  }
370
349
 
371
- /**
372
- * Compute fall-through expansions for each `case` in a `switch`. JavaScript
373
- * `switch` semantics say that once a case body executes, execution continues
374
- * into the bodies of subsequent cases until a `break` or terminal `return` is
375
- * hit. We pre-compute, per case, the flat list of statements that should run
376
- * when that case is the entry point — so downstream targets (which render each
377
- * case independently rather than executing fall-through at runtime) still
378
- * produce the right output.
379
- *
380
- * Walking right-to-left lets each case reuse the next case's already-expanded
381
- * tail without recomputation. Downstream nodes are deep-cloned when absorbed
382
- * so each case's expanded body owns its own AST subtree.
383
- *
384
- * @param {any[]} cases
385
- * @returns {Array<{ test: any, body: any[], source: any }>}
386
- */
387
- export function expand_switch_cases_for_fallthrough(cases) {
388
- /** @type {Array<{ test: any, body: any[], source: any }>} */
389
- const expanded = new Array(cases.length);
390
- for (let i = cases.length - 1; i >= 0; i--) {
391
- const consequent = flatten_switch_consequent(cases[i].consequent || []);
392
- const body = [];
393
- let has_terminal = false;
394
- for (const child of consequent) {
395
- if (child.type === 'BreakStatement') {
396
- has_terminal = true;
397
- break;
398
- }
399
- body.push(child);
400
- if (child.type === 'ReturnStatement') {
401
- has_terminal = true;
402
- break;
403
- }
404
- }
405
- // Strip locations from cloned downstream nodes. Only the original case
406
- // (one entry up the chain) keeps `loc`/`start`/`end`; clones inlined
407
- // into upstream cases would otherwise point editor IntelliSense at the
408
- // same source range multiple times (one hover/go-to-definition per
409
- // fall-through entry point), producing double/triple results in Volar.
410
- const downstream =
411
- !has_terminal && i + 1 < cases.length
412
- ? expanded[i + 1].body.map((n) => clone_expression_node(n, false))
413
- : [];
414
- expanded[i] = {
415
- test: cases[i].test,
416
- body: [...body, ...downstream],
417
- source: cases[i],
418
- };
419
- }
420
- return expanded;
421
- }
422
-
423
350
  /**
424
351
  * @param {AST.Expression | null | undefined} expression
425
352
  * @returns {boolean}
@@ -446,10 +373,8 @@ function is_static_string_expression(expression) {
446
373
  * When the expression is statically a non-null string at the AST level —
447
374
  * a string `Literal` (`"hello"`, `'hello'`) or a `TemplateLiteral` with no
448
375
  * interpolations (`` `hello` ``) — the coercion is provably a no-op and
449
- * the literal is emitted as-is. This covers both direct double-quoted
450
- * children (`<b>"hello"</b>`). Identifiers and any other expression type
451
- * still get the ternary because the AST alone can't prove they're non-null
452
- * strings.
376
+ * the literal is emitted as-is. Identifiers and any other expression type still
377
+ * get the ternary because the AST alone can't prove they're non-null strings.
453
378
  *
454
379
  * @param {AST.Expression} expression
455
380
  * @param {any} [source_node]
@@ -4,17 +4,20 @@
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 native template node whose parent is
9
- * a ripple `Element` will render as a JSX child of that element; anywhere else
10
- * it renders as a standalone expression (e.g. a return value).
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
9
+ * renders as a standalone expression (e.g. a return value).
11
10
  *
12
11
  * @param {any[]} path
13
12
  * @returns {boolean}
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
  /**