@tsrx/core 0.1.34 → 0.1.36

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 CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Core compiler infrastructure for TSRX syntax",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.1.34",
6
+ "version": "0.1.36",
7
7
  "type": "module",
8
8
  "repository": {
9
9
  "type": "git",
package/src/plugin.js CHANGED
@@ -259,6 +259,13 @@ export function TSRXPlugin(config) {
259
259
  // `#filterTemplateScriptContexts`.
260
260
  /** @type {number[]} */
261
261
  #expressionContainerContextBaselines = [];
262
+ // `#path` length at the start of each open `{ … }` expression container.
263
+ // Raw template text inside a container belongs only to an element opened
264
+ // inside it (`{<div> a</div>}`); at the container's own expression level
265
+ // (`{cond ? (<Outer>…</Outer>) : null}` after the `)`) the next characters
266
+ // are JS, and reading them as raw text would swallow tokens like `: null`.
267
+ /** @type {number[]} */
268
+ #expressionContainerPathBaselines = [];
262
269
  #consumeContainerBraceAfterScope = false;
263
270
  #scriptJSXElementDepth = 0;
264
271
  #forceScriptJSXElementDepth = 0;
@@ -936,6 +943,26 @@ export function TSRXPlugin(config) {
936
943
  if (!current_template_node || this.#isJSXControlFlowDirectiveAt(this.pos)) {
937
944
  return false;
938
945
  }
946
+ // Inside an expression container (only reachable with
947
+ // `allow_inside_expression_container`), raw text belongs to an element
948
+ // opened inside the container. When the innermost native template element
949
+ // sits below the container's path baseline we are at the container's own
950
+ // expression level — e.g. after `(<Outer>…</Outer>)` in
951
+ // `{cond ? (<Outer>…</Outer>) : null}` — and the following characters are
952
+ // JS tokens, not template text.
953
+ if (this.#jsxExpressionContainerDepth > 0 && !this.#openingNativeTemplateNode) {
954
+ const path_baseline = this.#expressionContainerPathBaselines.at(-1) ?? 0;
955
+ let inside_container = false;
956
+ for (let i = this.#path.length - 1; i >= path_baseline; i--) {
957
+ if (this.#isNativeTemplateNode(this.#path[i])) {
958
+ inside_container = true;
959
+ break;
960
+ }
961
+ }
962
+ if (!inside_container) {
963
+ return false;
964
+ }
965
+ }
939
966
  if (this.#isTemplateLineCommentStart(this.pos)) {
940
967
  return false;
941
968
  }
@@ -3377,6 +3404,7 @@ export function TSRXPlugin(config) {
3377
3404
  // container must not strip anything below this floor (see
3378
3405
  // `#filterTemplateScriptContexts`).
3379
3406
  this.#expressionContainerContextBaselines.push(this.context.length);
3407
+ this.#expressionContainerPathBaselines.push(this.#path.length);
3380
3408
  pushed_context_baseline = true;
3381
3409
 
3382
3410
  node.expression =
@@ -3398,6 +3426,7 @@ export function TSRXPlugin(config) {
3398
3426
  this.#jsxExpressionContainerDepth--;
3399
3427
  if (pushed_context_baseline) {
3400
3428
  this.#expressionContainerContextBaselines.pop();
3429
+ this.#expressionContainerPathBaselines.pop();
3401
3430
  }
3402
3431
  }
3403
3432
 
package/src/scope.js CHANGED
@@ -63,10 +63,9 @@ export function create_scopes(ast, root, parent, error_options) {
63
63
  const scope = state.scope.child(true);
64
64
  scopes.set(node, scope);
65
65
 
66
- if (node.type === 'ForOfStatement') {
67
- if (node.index) {
68
- state.scope.declare(node.index, 'normal', 'let');
69
- }
66
+ // Only a `@for` directive can declare an `index` binding.
67
+ if (node.type === 'JSXForExpression' && node.index) {
68
+ state.scope.declare(node.index, 'normal', 'let');
70
69
  }
71
70
 
72
71
  next({ scope });
@@ -181,6 +180,21 @@ export function create_scopes(ast, root, parent, error_options) {
181
180
  SwitchStatement: create_block_scope,
182
181
  JSXForExpression: create_block_scope,
183
182
  JSXSwitchExpression: create_block_scope,
183
+ // Each `@{ … }` code block is its own lexical scope, whether it is a
184
+ // function body or a template child — its bindings never leak out.
185
+ JSXCodeBlock(node, context) {
186
+ const parent = context.path.at(-1);
187
+ if (
188
+ parent?.type === 'FunctionDeclaration' ||
189
+ parent?.type === 'FunctionExpression' ||
190
+ parent?.type === 'ArrowFunctionExpression'
191
+ ) {
192
+ // The function already created a scope for its body.
193
+ context.next();
194
+ } else {
195
+ create_block_scope(node, context);
196
+ }
197
+ },
184
198
  BlockStatement(node, context) {
185
199
  const parent = context.path.at(-1);
186
200
  if (
@@ -243,7 +243,6 @@ export function tsx_with_ts_locations() {
243
243
  export function is_template_if_node(node) {
244
244
  return (
245
245
  node?.type === 'JSXIfExpression' ||
246
- node?.metadata?.tsrxDirective === 'if' ||
247
246
  (node?.type === 'IfStatement' && node?.statementType === 'IfStatement')
248
247
  );
249
248
  }
@@ -255,7 +254,6 @@ export function is_template_if_node(node) {
255
254
  export function is_template_for_of_node(node) {
256
255
  return (
257
256
  node?.type === 'JSXForExpression' ||
258
- node?.metadata?.tsrxDirective === 'for' ||
259
257
  (node?.type === 'ForOfStatement' && node?.statementType === 'ForOfStatement')
260
258
  );
261
259
  }
@@ -267,7 +265,6 @@ export function is_template_for_of_node(node) {
267
265
  export function is_template_switch_node(node) {
268
266
  return (
269
267
  node?.type === 'JSXSwitchExpression' ||
270
- node?.metadata?.tsrxDirective === 'switch' ||
271
268
  (node?.type === 'SwitchStatement' && node?.statementType === 'SwitchStatement')
272
269
  );
273
270
  }
@@ -279,7 +276,6 @@ export function is_template_switch_node(node) {
279
276
  export function is_template_try_node(node) {
280
277
  return (
281
278
  node?.type === 'JSXTryExpression' ||
282
- node?.metadata?.tsrxDirective === 'try' ||
283
279
  (node?.type === 'TryStatement' && node?.statementType === 'TryStatement')
284
280
  );
285
281
  }
@@ -2302,7 +2302,7 @@ function create_style_anchor_name(transform_context) {
2302
2302
  }
2303
2303
 
2304
2304
  /**
2305
- * @param {ESTreeJSX.JSXElement} element
2305
+ * @param {AST.TSRXJSXElement} element
2306
2306
  */
2307
2307
  function disable_style_anchor_verification(element) {
2308
2308
  if (element.openingElement?.name) {
@@ -368,26 +368,6 @@ export function literal(value, raw, loc_info) {
368
368
  return set_location(node, loc_info);
369
369
  }
370
370
 
371
- /**
372
- * Ripple template `Text` node wrapping a string literal of `value`. Used by the
373
- * Ripple normalizer when lowering a JSX text child to a template Text node.
374
- *
375
- * Remove once Ripple transformers move to the parser's JSX AST
376
- *
377
- * @param {string} value
378
- * @param {AST.NodeWithLocation} [loc_info]
379
- * @returns {AST.Text}
380
- */
381
- export function text(value, loc_info) {
382
- const node = /** @type {AST.Text} */ ({
383
- type: 'Text',
384
- expression: literal(value, JSON.stringify(value), loc_info),
385
- metadata: { path: [] },
386
- });
387
-
388
- return set_location(node, loc_info);
389
- }
390
-
391
371
  /**
392
372
  * @param {AST.Expression | AST.Super} object
393
373
  * @param {string | AST.Expression | AST.PrivateIdentifier} property
@@ -1219,10 +1199,13 @@ export function jsx_element_fresh(
1219
1199
  }
1220
1200
 
1221
1201
  /**
1222
- * @param {ESTreeJSX.JSXElement} node
1223
- * @param {ESTreeJSX.JSXOpeningElement['attributes']} attributes
1224
- * @param {ESTreeJSX.JSXElement['children']} children
1225
- * @returns {ESTreeJSX.JSXElement}
1202
+ * Elements carry the parser's widened TSRX shape (dynamic-tag names, lowered
1203
+ * template children) — see {@link jsx_fragment}. A `JSXStyleElement` shares
1204
+ * the element shape and may be re-emitted as an empty `<style>` element.
1205
+ * @param {AST.TSRXJSXElement | AST.JSXStyleElement} node
1206
+ * @param {ESTreeJSX.TSRXJSXOpeningElement['attributes']} attributes
1207
+ * @param {AST.TSRXJSXElement['children']} children
1208
+ * @returns {AST.TSRXJSXElement}
1226
1209
  */
1227
1210
  export function jsx_element(node, attributes = [], children = []) {
1228
1211
  return {
@@ -1248,9 +1231,12 @@ export function jsx_element(node, attributes = [], children = []) {
1248
1231
  }
1249
1232
 
1250
1233
  /**
1251
- * @param {ESTreeJSX.JSXFragment['children']} children
1234
+ * Fragments carry the parser's widened TSRX shape: template lowering places
1235
+ * any node in `children` (statement blocks, directives, code-block IIFEs),
1236
+ * not just printable JSX children.
1237
+ * @param {AST.TSRXJSXFragment['children']} children
1252
1238
  * @param {ESTreeJSX.JSXOpeningFragment['attributes']} [attributes]
1253
- * @returns {ESTreeJSX.JSXFragment}
1239
+ * @returns {AST.TSRXJSXFragment}
1254
1240
  */
1255
1241
  export function jsx_fragment(children = [], attributes = []) {
1256
1242
  return {
@@ -1269,44 +1255,6 @@ export function jsx_fragment(children = [], attributes = []) {
1269
1255
  };
1270
1256
  }
1271
1257
 
1272
- /**
1273
- * Ripple's internal fragment template node (the normalized form of a
1274
- * `JSXFragment`).
1275
- * @param {AST.Node[]} [children]
1276
- * @param {AST.NodeWithLocation} [loc_info]
1277
- * @returns {AST.TsrxFragment}
1278
- */
1279
- export function tsrx_fragment(children = [], loc_info) {
1280
- const node = /** @type {AST.TsrxFragment} */ (
1281
- /** @type {unknown} */ ({
1282
- type: 'TsrxFragment',
1283
- children,
1284
- attributes: [],
1285
- selfClosing: false,
1286
- metadata: { path: [] },
1287
- })
1288
- );
1289
-
1290
- return set_location(node, loc_info);
1291
- }
1292
-
1293
- /**
1294
- * Ripple's internal expression template child (the normalized form of a
1295
- * `JSXExpressionContainer` child).
1296
- * @param {AST.Expression} expression
1297
- * @param {AST.NodeWithLocation} [loc_info]
1298
- * @returns {AST.TSRXExpression}
1299
- */
1300
- export function tsrx_expression(expression, loc_info) {
1301
- const node = /** @type {AST.TSRXExpression} */ ({
1302
- type: 'TSRXExpression',
1303
- expression,
1304
- metadata: { path: [] },
1305
- });
1306
-
1307
- return set_location(node, loc_info);
1308
- }
1309
-
1310
1258
  /**
1311
1259
  * @param {AST.Expression | ESTreeJSX.JSXEmptyExpression} expression
1312
1260
  * @param {AST.NodeWithLocation} [loc_info]
package/types/index.d.ts CHANGED
@@ -88,7 +88,28 @@ interface BaseNodeMetaData {
88
88
  dynamicElement?: boolean;
89
89
  templateMode?: 'script' | 'template';
90
90
  script_only?: boolean;
91
- tsrxDirective?: 'if' | 'for' | 'switch' | 'try';
91
+ /** A synthetic wrapper for a nested `@{ @{ ... } }` code-block render chain. */
92
+ tsrx_code_block_chain?: boolean;
93
+ /** A synthesized render-body fragment (see create_native_tsrx_render_function). */
94
+ tsrx_render_fragment?: boolean;
95
+ /** A merged text run produced by normalize_children - renders through the text path. */
96
+ tsrx_text?: boolean;
97
+ /** Control-flow node renders as the sole child of its parent (controlled anchor). */
98
+ is_controlled?: boolean;
99
+ /** Control-flow node is the root of a render body - anchors on `__anchor`. */
100
+ root_controlled?: boolean;
101
+ /** Append target set by transform_children when all siblings are static component children. */
102
+ append_into?: AST.Identifier;
103
+ /** Generated pattern id substituted for a destructured keyed `@for` left. */
104
+ tsrx_for_pattern_id?: AST.Identifier;
105
+ /** Memoized render-body statements (see get_native_tsrx_function_body). */
106
+ tsrx_render_body?: AST.Node[];
107
+ /** Memoized resolved render slot of a `@{ … }` code block (see get_code_block_render). */
108
+ tsrx_render_slot?: { render: AST.Node | null };
109
+ /** Memoized template-child lowering of a `@{ … }` code block (see get_code_block_template_child). */
110
+ tsrx_template_child?: { child: AST.Node | null };
111
+ /** Memoized `<> … </>` wrapper for a value-position directive (see get_directive_value_wrapper). */
112
+ tsrx_value_wrapper?: AST.TSRXJSXFragment;
92
113
  ts_name?: string;
93
114
  delegated?: any;
94
115
  returned_tsrx_return?: AST.ReturnStatement;
@@ -122,6 +143,10 @@ interface FunctionMetaData extends BaseNodeMetaData {
122
143
  is_method?: boolean;
123
144
  tracked?: boolean;
124
145
  has_lazy_descendants?: boolean;
146
+ /** The component's extracted `<style>` stylesheet (element-level scoped-class info lives on BaseNodeMetaData's `css`). */
147
+ component_css?: AST.CSS.StyleSheet | null;
148
+ /** Top-level scoped classes collected while pruning the component's CSS. */
149
+ topScopedClasses?: TopScopedClasses;
125
150
  synthetic_children?: boolean;
126
151
  generated_helpers?: any[];
127
152
  generated_statics?: any[];
@@ -248,30 +273,17 @@ declare module 'estree' {
248
273
  tracked?: boolean;
249
274
  }
250
275
 
251
- interface SimpleLiteral extends AST.LiteralNode {}
252
- interface RegExpLiteral extends AST.LiteralNode {}
253
- interface BigIntLiteral extends AST.LiteralNode {}
254
-
255
276
  interface TrackedNode {
256
277
  tracked?: boolean;
257
278
  }
258
279
 
259
- interface LiteralNode {
260
- was_expression?: boolean;
261
- }
262
-
263
280
  // Include TypeScript node types and TSRX-specific nodes in NodeMap
264
281
  interface NodeMap {
265
- Element: Element;
266
- TsrxFragment: TsrxFragment;
267
- Text: Text;
282
+ JSXSpreadChild: ESTreeJSX.JSXSpreadChild;
268
283
  TSRXJSXElement: TSRXJSXElement;
269
284
  TSRXJSXFragment: TSRXJSXFragment;
270
285
  TSRXJSXOpeningElement: ESTreeJSX.TSRXJSXOpeningElement;
271
286
  TSRXJSXClosingElement: ESTreeJSX.TSRXJSXClosingElement;
272
- TSRXExpression: TSRXExpression;
273
- Attribute: Attribute;
274
- SpreadAttribute: SpreadAttribute;
275
287
  JSXCodeBlock: JSXCodeBlock;
276
288
  JSXStyleElement: JSXStyleElement;
277
289
  JSXIfExpression: JSXIfExpression;
@@ -282,10 +294,8 @@ declare module 'estree' {
282
294
  }
283
295
 
284
296
  interface ExpressionMap {
285
- Element: Element;
286
- TsrxFragment: TsrxFragment;
287
- Text: Text;
288
- TSRXExpression: TSRXExpression;
297
+ TSRXJSXElement: TSRXJSXElement;
298
+ TSRXJSXFragment: TSRXJSXFragment;
289
299
  JSXCodeBlock: JSXCodeBlock;
290
300
  JSXStyleElement: JSXStyleElement;
291
301
  JSXIfExpression: JSXIfExpression;
@@ -299,69 +309,6 @@ declare module 'estree' {
299
309
 
300
310
  type TraversableAstNode = AST.Node & Record<string, unknown>;
301
311
 
302
- // Ripple-normalized template node shapes. The core parser emits JSX-shaped
303
- // TSRX nodes; @tsrx/ripple creates these during its normalization pass.
304
- interface Attribute extends AST.BaseNode {
305
- type: 'Attribute';
306
- name: AST.Identifier;
307
- value: any;
308
- shorthand?: boolean;
309
- metadata: BaseNodeMetaData;
310
- }
311
-
312
- interface SpreadAttribute extends AST.BaseNode {
313
- type: 'SpreadAttribute';
314
- argument: AST.Expression;
315
- metadata: BaseNodeMetaData;
316
- }
317
-
318
- interface Element extends AST.BaseExpression {
319
- type: 'Element';
320
- id: AST.Expression;
321
- attributes: Array<Attribute | SpreadAttribute>;
322
- children: AST.Node[];
323
- openingElement: ESTreeJSX.JSXOpeningElement;
324
- closingElement: ESTreeJSX.JSXClosingElement | null;
325
- selfClosing?: boolean;
326
- unclosed?: boolean;
327
- isDynamic?: boolean;
328
- css?: string;
329
- metadata: BaseNodeMetaData;
330
- start: number;
331
- end: number;
332
- }
333
-
334
- interface TsrxFragment extends AST.BaseExpression {
335
- type: 'TsrxFragment';
336
- children: AST.Node[];
337
- openingElement?: ESTreeJSX.JSXOpeningFragment;
338
- closingElement?: ESTreeJSX.JSXClosingFragment | null;
339
- selfClosing?: boolean;
340
- attributes?: Array<Attribute | SpreadAttribute>;
341
- metadata: BaseNodeMetaData & {
342
- /**
343
- * A synthetic wrapper for a nested code-block render chain
344
- * (`@{ @{ … } }`), so render-slot consumers see a template node;
345
- * template-children lowering unwraps it.
346
- */
347
- tsrx_code_block_chain?: boolean;
348
- };
349
- start: number;
350
- end: number;
351
- }
352
-
353
- interface Text extends AST.BaseExpression {
354
- type: 'Text';
355
- expression: AST.Expression;
356
- metadata: BaseNodeMetaData;
357
- }
358
-
359
- interface TSRXExpression extends AST.BaseExpression {
360
- type: 'TSRXExpression';
361
- expression: AST.Expression;
362
- metadata: BaseNodeMetaData;
363
- }
364
-
365
312
  type TSRXJSXChild =
366
313
  | ESTreeJSX.JSXText
367
314
  | ESTreeJSX.JSXExpressionContainer
@@ -376,7 +323,16 @@ declare module 'estree' {
376
323
  AST.NodeWithMaybeComments {
377
324
  openingElement: ESTreeJSX.TSRXJSXOpeningElement;
378
325
  closingElement: ESTreeJSX.TSRXJSXClosingElement | null;
379
- children: TSRXJSXChild[];
326
+ /** The parser marks dynamic `<{expr}>` tags; lower_dynamic_element clears it on its copy. */
327
+ isDynamic?: boolean;
328
+ /** Loose-mode recovery: the element was never closed. */
329
+ unclosed?: boolean;
330
+ /**
331
+ * The parser emits {@link TSRXJSXChild}; the compile pre-passes lower
332
+ * template children in place (retyped directives, code-block IIFEs,
333
+ * merged text runs), so any node can appear here by transform time.
334
+ */
335
+ children: AST.Node[];
380
336
  metadata: BaseNodeMetaData & {
381
337
  ts_name?: string;
382
338
  };
@@ -384,7 +340,8 @@ declare module 'estree' {
384
340
 
385
341
  interface TSRXJSXFragment
386
342
  extends Omit<ESTreeJSX.JSXFragment, 'children'>, AST.NodeWithMaybeComments {
387
- children: TSRXJSXChild[];
343
+ /** See {@link TSRXJSXElement}'s `children`. */
344
+ children: AST.Node[];
388
345
  }
389
346
 
390
347
  interface JSXCodeBlock extends AST.BaseExpression {
@@ -411,22 +368,39 @@ declare module 'estree' {
411
368
  metadata: BaseNodeMetaData;
412
369
  }
413
370
 
414
- interface JSXForExpression extends AST.BaseExpression {
371
+ interface JSXForExpressionBase extends AST.BaseExpression {
415
372
  type: 'JSXForExpression';
416
- statementType: 'ForStatement' | 'ForInStatement' | 'ForOfStatement';
417
- body: AST.Statement;
418
- init?: AST.VariableDeclaration | AST.Expression | null;
419
- test?: AST.Expression | null;
420
- update?: AST.Expression | null;
421
- left?: AST.VariableDeclaration | AST.Pattern;
422
- right?: AST.Expression;
423
- await?: boolean;
373
+ /** The parser raises unless the directive body is a `{ … }` block. */
374
+ body: AST.BlockStatement;
424
375
  index?: AST.Identifier | null;
425
376
  key?: AST.Expression | null;
426
377
  empty?: AST.BlockStatement | null;
427
378
  metadata: BaseNodeMetaData;
428
379
  }
429
380
 
381
+ interface JSXForOfExpression extends JSXForExpressionBase {
382
+ statementType: 'ForOfStatement';
383
+ left: AST.VariableDeclaration | AST.Pattern;
384
+ right: AST.Expression;
385
+ await?: boolean;
386
+ }
387
+
388
+ interface JSXForInExpression extends JSXForExpressionBase {
389
+ statementType: 'ForInStatement';
390
+ left: AST.VariableDeclaration | AST.Pattern;
391
+ right: AST.Expression;
392
+ }
393
+
394
+ interface JSXForPlainExpression extends JSXForExpressionBase {
395
+ statementType: 'ForStatement';
396
+ init?: AST.VariableDeclaration | AST.Expression | null;
397
+ test?: AST.Expression | null;
398
+ update?: AST.Expression | null;
399
+ }
400
+
401
+ /** `@for` — discriminated on `statementType` (for-of / for-in / for(;;)). */
402
+ type JSXForExpression = JSXForOfExpression | JSXForInExpression | JSXForPlainExpression;
403
+
430
404
  interface JSXSwitchExpression extends AST.BaseExpression {
431
405
  type: 'JSXSwitchExpression';
432
406
  statementType: 'SwitchStatement';
@@ -445,6 +419,13 @@ declare module 'estree' {
445
419
  metadata: BaseNodeMetaData;
446
420
  }
447
421
 
422
+ /** A `@if`/`@for`/`@switch`/`@try` template control-flow directive. */
423
+ type JSXTemplateDirective =
424
+ | JSXIfExpression
425
+ | JSXForExpression
426
+ | JSXSwitchExpression
427
+ | JSXTryExpression;
428
+
448
429
  interface ParenthesizedExpression extends AST.BaseNode {
449
430
  type: 'ParenthesizedExpression';
450
431
  expression: AST.Expression;
@@ -764,11 +745,27 @@ declare module 'estree-jsx' {
764
745
  }
765
746
 
766
747
  interface TSRXJSXOpeningElement extends Omit<JSXOpeningElement, 'name'> {
767
- name: AST.MemberExpression | JSXIdentifier | JSXNamespacedName | JSXExpressionContainer;
748
+ /** The parser marks dynamic `<{expr}>` tags; lower_dynamic_element clears it on its copy. */
749
+ isDynamic?: boolean;
750
+ // AST.MemberExpression: the parser never produces it, but the to_ts
751
+ // transform plants the visited member chain (`<Foo.Bar>`) into the name
752
+ // slot for the TSX printer and its source mappings.
753
+ name:
754
+ | JSXMemberExpression
755
+ | JSXIdentifier
756
+ | JSXNamespacedName
757
+ | JSXExpressionContainer
758
+ | import('estree').MemberExpression;
768
759
  }
769
760
 
770
761
  interface TSRXJSXClosingElement extends Omit<JSXClosingElement, 'name'> {
771
- name: AST.MemberExpression | JSXIdentifier | JSXNamespacedName | JSXExpressionContainer;
762
+ // See TSRXJSXOpeningElement's `name`.
763
+ name:
764
+ | JSXMemberExpression
765
+ | JSXIdentifier
766
+ | JSXNamespacedName
767
+ | JSXExpressionContainer
768
+ | import('estree').MemberExpression;
772
769
  }
773
770
 
774
771
  interface ExpressionMap {
@@ -1116,8 +1113,10 @@ declare module 'estree' {
1116
1113
  > {
1117
1114
  typeAnnotation: TypeNode;
1118
1115
  }
1119
- interface TSTypeAssertion extends AcornTSNode<TSESTree.TSTypeAssertion> {
1116
+ interface TSTypeAssertion extends Omit<AcornTSNode<TSESTree.TSTypeAssertion>, 'typeAnnotation'> {
1117
+ // Have to override it to use our Expression for required properties like metadata
1120
1118
  expression: AST.Expression;
1119
+ typeAnnotation: TypeNode;
1121
1120
  }
1122
1121
  interface TSTypeLiteral extends Omit<AcornTSNode<TSESTree.TSTypeLiteral>, 'members'> {
1123
1122
  members: TypeElement[];
@@ -1452,7 +1451,7 @@ export interface AnalysisState extends BaseState {
1452
1451
  filename: string;
1453
1452
  };
1454
1453
  };
1455
- elements?: Array<ESTreeJSX.JSXElement | AST.Element>;
1454
+ elements?: Array<AST.TSRXJSXElement | AST.JSXStyleElement>;
1456
1455
  function_depth?: number;
1457
1456
  collect?: boolean;
1458
1457
  metadata: BaseStateMetaData & {
@@ -1531,7 +1530,25 @@ export interface TransformClientState extends BaseState {
1531
1530
  }
1532
1531
 
1533
1532
  /** Override zimmerframe types and provide our own */
1534
- type NodeOf<T extends string, X> = X extends { type: T } ? X : never;
1533
+ /**
1534
+ * Where stock `@types/estree-jsx` and the TSRX parser shapes share a `type`
1535
+ * tag, visitors receive the TSRX shape — the parser only ever produces that
1536
+ * one (dynamic tag names, code-block children, `metadata`, `start`/`end`).
1537
+ * Interface merging cannot widen the stock interfaces' property types, so the
1538
+ * TSRXJSX* variants override the plain ones here instead.
1539
+ */
1540
+ interface VisitorNodeOverrides {
1541
+ JSXElement: AST.TSRXJSXElement;
1542
+ JSXFragment: AST.TSRXJSXFragment;
1543
+ JSXOpeningElement: ESTreeJSX.TSRXJSXOpeningElement;
1544
+ JSXClosingElement: ESTreeJSX.TSRXJSXClosingElement;
1545
+ }
1546
+
1547
+ type NodeOf<T extends string, X> = T extends keyof VisitorNodeOverrides
1548
+ ? VisitorNodeOverrides[T]
1549
+ : X extends { type: T }
1550
+ ? X
1551
+ : never;
1535
1552
 
1536
1553
  type SpecializedVisitors<T extends AST.Node | AST.CSS.Node, U> = {
1537
1554
  [K in T['type']]?: Visitor<NodeOf<K, T>, U, T>;
@@ -1545,7 +1562,12 @@ export type CatchAllVisitor<T, U, V> = (
1545
1562
  visit: VisitFn<V>,
1546
1563
  ) => V | void;
1547
1564
 
1548
- export type Visitor<T, U, V> = (node: T, context: Context<V, U>) => V | void;
1565
+ /**
1566
+ * A visitor may replace a node with several: zimmerframe stores the returned
1567
+ * array verbatim in the parent's statement list and the printer flattens
1568
+ * nested statement arrays.
1569
+ */
1570
+ export type Visitor<T, U, V> = (node: T, context: Context<V, U>) => V | V[] | void;
1549
1571
 
1550
1572
  export type Visitors<T extends AST.Node | AST.CSS.Node, U> = T['type'] extends '_'
1551
1573
  ? never