@tsrx/core 0.1.45 → 0.1.47

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,7 +1,7 @@
1
1
  /** @import * as AST from 'estree' */
2
2
 
3
3
  import * as b from '../utils/builders.js';
4
- import { is_function_or_component_node } from '../utils/ast.js';
4
+ import { has_location, is_function_or_component_node } from '../utils/ast.js';
5
5
 
6
6
  /**
7
7
  * Lazy destructuring transform — framework-agnostic.
@@ -57,7 +57,7 @@ function generate_lazy_id(context) {
57
57
  * @returns {any}
58
58
  */
59
59
  function set_source_location(node, loc_info) {
60
- if (loc_info?.loc) {
60
+ if (has_location(loc_info)) {
61
61
  node.start = loc_info.start;
62
62
  node.end = loc_info.end;
63
63
  node.loc = loc_info.loc;
@@ -84,7 +84,7 @@ function create_generated_identifier(name, loc_info, source_name, source_length)
84
84
  * @returns {{ start: number, end: number, loc: any, source_length: number } | null}
85
85
  */
86
86
  function get_lazy_pattern_mapping_range(pattern) {
87
- if (!pattern.loc) return null;
87
+ if (!has_location(pattern)) return null;
88
88
 
89
89
  const end = pattern.typeAnnotation?.start ?? pattern.end;
90
90
  const end_loc = pattern.typeAnnotation?.loc?.start ?? pattern.loc.end;
@@ -148,11 +148,11 @@ function set_lazy_param_binding_mappings(lazy_id, pattern) {
148
148
 
149
149
  const value = prop.value;
150
150
  const actual = value.type === 'AssignmentPattern' ? value.left : value;
151
- if (actual.type !== 'Identifier' || !actual.loc) continue;
151
+ if (actual.type !== 'Identifier' || !has_location(actual)) continue;
152
152
 
153
153
  const key_name = get_static_property_name(prop.key);
154
154
  const generated = key_name == null ? null : type_keys.get(key_name);
155
- if (generated?.loc) {
155
+ if (has_location(generated)) {
156
156
  generated.metadata = { ...generated.metadata, disable_verification: true };
157
157
  mappings.push({ source: actual, generated });
158
158
  }
@@ -65,6 +65,7 @@ import {
65
65
  get_mapping_from_node,
66
66
  } from '../source-map-utils.js';
67
67
  import { should_preserve_comment } from '../comment-utils.js';
68
+ import { has_location } from '../utils/ast.js';
68
69
 
69
70
  const LAZY_PARAM_IDENTIFIER_REGEX = /^__lazy\d+$/;
70
71
 
@@ -453,8 +454,8 @@ export function convert_source_map_to_mappings(
453
454
  * @returns {void}
454
455
  */
455
456
  function set_bracket_computed_mapping(node, mappings) {
456
- if (node.loc) {
457
- const key = /** @type {typeof node.key & AST.NodeWithLocation} */ (node.key);
457
+ if (has_location(node.key)) {
458
+ const key = node.key;
458
459
  mappings.push(
459
460
  get_mapping_from_node(
460
461
  /** @type {AST.NodeWithLocation} */ ({
@@ -513,7 +514,7 @@ export function convert_source_map_to_mappings(
513
514
  * @param {AST.Literal} node
514
515
  */
515
516
  function handle_literal(node) {
516
- if (node.loc) {
517
+ if (has_location(node)) {
517
518
  const mapping = get_mapping_from_node(node, src_to_gen_map, gen_line_offsets);
518
519
  mappings.push(mapping);
519
520
  }
@@ -524,12 +525,15 @@ export function convert_source_map_to_mappings(
524
525
  * @returns {void}
525
526
  */
526
527
  function add_extra_source_mapping_tokens(generated_node) {
527
- if (!generated_node?.loc || !Array.isArray(generated_node.metadata?.extra_source_mappings)) {
528
+ if (
529
+ !has_location(generated_node) ||
530
+ !Array.isArray(generated_node.metadata?.extra_source_mappings)
531
+ ) {
528
532
  return;
529
533
  }
530
534
 
531
535
  for (const source_node of generated_node.metadata.extra_source_mappings) {
532
- if (!source_node?.loc) continue;
536
+ if (!has_location(source_node)) continue;
533
537
 
534
538
  tokens.push({
535
539
  source: source_node.name ?? generated_node.name,
@@ -537,10 +541,7 @@ export function convert_source_map_to_mappings(
537
541
  loc: source_node.loc,
538
542
  generatedLoc: generated_node.loc,
539
543
  metadata: {},
540
- sourceLength:
541
- typeof source_node.start === 'number' && typeof source_node.end === 'number'
542
- ? source_node.end - source_node.start
543
- : undefined,
544
+ sourceLength: source_node.end - source_node.start,
544
545
  });
545
546
  }
546
547
  }
@@ -602,7 +603,7 @@ export function convert_source_map_to_mappings(
602
603
  for (const binding_mapping of node.metadata.lazy_param_binding_mappings) {
603
604
  const source_node = binding_mapping.source;
604
605
  const generated_node = binding_mapping.generated;
605
- if (!source_node?.loc || !generated_node?.loc) continue;
606
+ if (!has_location(source_node) || !has_location(generated_node)) continue;
606
607
 
607
608
  const mapping = get_mapping_from_node(
608
609
  generated_node,
@@ -610,8 +611,8 @@ export function convert_source_map_to_mappings(
610
611
  gen_line_offsets,
611
612
  mapping_data_verify_only,
612
613
  );
613
- const source_start = /** @type {number} */ (source_node.start);
614
- const source_end = /** @type {number} */ (source_node.end);
614
+ const source_start = source_node.start;
615
+ const source_end = source_node.end;
615
616
  mapping.sourceOffsets = [source_start];
616
617
  mapping.lengths = [source_end - source_start];
617
618
  mappings.push(mapping);
@@ -649,7 +650,7 @@ export function convert_source_map_to_mappings(
649
650
  // We only map the 'import' and the last character
650
651
  // to avoid overlapping with individual specifier mappings
651
652
  // which would interfere when only SOME imports are unused.
652
- if (node.loc) {
653
+ if (has_location(node)) {
653
654
  tokens.push({
654
655
  source: 'import',
655
656
  generated: 'import',
@@ -842,7 +843,7 @@ export function convert_source_map_to_mappings(
842
843
  }
843
844
  return;
844
845
  } else if (node.type === 'JSXExpressionContainer') {
845
- if (node.loc) {
846
+ if (has_location(node)) {
846
847
  mappings.push(
847
848
  get_mapping_from_node(node, src_to_gen_map, gen_line_offsets, mapping_data_verify_only),
848
849
  );
@@ -929,9 +930,9 @@ export function convert_source_map_to_mappings(
929
930
  }
930
931
  }
931
932
 
932
- if ((closing?.loc || opening.loc) && (closing || opening.selfClosing)) {
933
+ const target_node = closing ?? opening;
934
+ if (has_location(target_node) && (closing || opening.selfClosing)) {
933
935
  // Add the whole closing tag or the self-closing.
934
- const target_node = closing ? closing : opening;
935
936
  const mapping = get_mapping_from_node(
936
937
  target_node,
937
938
  src_to_gen_map,
@@ -948,7 +949,7 @@ export function convert_source_map_to_mappings(
948
949
  mapping.generatedLengths = [mapping.generatedLengths[0] + 1];
949
950
  if (!closing && opening.selfClosing) {
950
951
  const generated_close_length = '/>;'.length;
951
- mapping.sourceOffsets = [/** @type {AST.NodeWithLocation} */ (opening).end - 2];
952
+ mapping.sourceOffsets = [target_node.end - 2];
952
953
  mapping.lengths = ['/>'.length];
953
954
  mapping.generatedOffsets = [
954
955
  mapping.generatedOffsets[0] + mapping.generatedLengths[0] - generated_close_length,
@@ -970,7 +971,7 @@ export function convert_source_map_to_mappings(
970
971
  ) {
971
972
  const is_method = node.metadata?.is_method;
972
973
 
973
- if (node.type === 'ArrowFunctionExpression' && node.loc) {
974
+ if (node.type === 'ArrowFunctionExpression' && has_location(node)) {
974
975
  // The printer emits node-level boundary markers for arrows (their
975
976
  // span can start at a bare `(`), so the strict lookup always
976
977
  // resolves — no defensive has() guard.
@@ -1258,7 +1259,7 @@ export function convert_source_map_to_mappings(
1258
1259
  visit(node.empty);
1259
1260
  }
1260
1261
 
1261
- if (node.loc) {
1262
+ if (has_location(node)) {
1262
1263
  mappings.push(
1263
1264
  get_mapping_from_node(node, src_to_gen_map, gen_line_offsets, mapping_data_verify_only),
1264
1265
  );
@@ -1328,7 +1329,7 @@ export function convert_source_map_to_mappings(
1328
1329
  }
1329
1330
  return;
1330
1331
  } else if (node.type === 'CallExpression' || node.type === 'NewExpression') {
1331
- if (node.type === 'NewExpression' && node.loc) {
1332
+ if (node.type === 'NewExpression' && has_location(node)) {
1332
1333
  mappings.push(
1333
1334
  get_mapping_from_node(node, src_to_gen_map, gen_line_offsets, mapping_data_verify_only),
1334
1335
  );
@@ -1358,7 +1359,7 @@ export function convert_source_map_to_mappings(
1358
1359
  }
1359
1360
  return;
1360
1361
  } else if (node.type === 'MemberExpression') {
1361
- if (node.loc) {
1362
+ if (has_location(node)) {
1362
1363
  const mapping = get_mapping_from_node(
1363
1364
  node,
1364
1365
  src_to_gen_map,
@@ -1375,7 +1376,7 @@ export function convert_source_map_to_mappings(
1375
1376
  if (node.property) {
1376
1377
  visit(node.property);
1377
1378
 
1378
- if (node.computed && node.property.loc) {
1379
+ if (node.computed && has_location(node.property)) {
1379
1380
  mappings.push(
1380
1381
  get_mapping_from_node(
1381
1382
  node.property,
@@ -1411,7 +1412,7 @@ export function convert_source_map_to_mappings(
1411
1412
 
1412
1413
  return;
1413
1414
  } else if (node.type === 'ObjectExpression' || node.type === 'ObjectPattern') {
1414
- if (node.loc && node.type === 'ObjectExpression') {
1415
+ if (node.type === 'ObjectExpression' && has_location(node)) {
1415
1416
  mappings.push(
1416
1417
  get_mapping_from_node(node, src_to_gen_map, gen_line_offsets, mapping_data_verify_only),
1417
1418
  );
@@ -1474,7 +1475,7 @@ export function convert_source_map_to_mappings(
1474
1475
  }
1475
1476
  return;
1476
1477
  } else if (node.type === 'TemplateLiteral') {
1477
- if (node.loc) {
1478
+ if (has_location(node)) {
1478
1479
  mappings.push(
1479
1480
  get_mapping_from_node(node, src_to_gen_map, gen_line_offsets, mapping_data_verify_only),
1480
1481
  );
@@ -1505,7 +1506,7 @@ export function convert_source_map_to_mappings(
1505
1506
  visit(node.argument);
1506
1507
  }
1507
1508
 
1508
- if (node.type === 'ReturnStatement' && node.loc) {
1509
+ if (node.type === 'ReturnStatement' && has_location(node)) {
1509
1510
  const mapping = get_mapping_from_node(
1510
1511
  node,
1511
1512
  src_to_gen_map,
@@ -1545,7 +1546,7 @@ export function convert_source_map_to_mappings(
1545
1546
  }
1546
1547
  }
1547
1548
 
1548
- if (node.loc) {
1549
+ if (has_location(node)) {
1549
1550
  mappings.push(
1550
1551
  get_mapping_from_node(node, src_to_gen_map, gen_line_offsets, mapping_data_verify_only),
1551
1552
  );
@@ -1762,7 +1763,7 @@ export function convert_source_map_to_mappings(
1762
1763
  }
1763
1764
  return;
1764
1765
  } else if (node.type === 'ParenthesizedExpression') {
1765
- if (node.metadata.forceMapping && node.loc) {
1766
+ if (node.metadata.forceMapping && has_location(node)) {
1766
1767
  const mapping = get_mapping_from_node(node, src_to_gen_map, gen_line_offsets);
1767
1768
  if (node.metadata.skipParenthesisMapping) {
1768
1769
  mapping.generatedOffsets[0] = mapping.generatedOffsets[0] + 1; // Skip the opening parenthesis
@@ -1801,7 +1802,7 @@ export function convert_source_map_to_mappings(
1801
1802
  node.type === 'TSTypeParameterInstantiation' ||
1802
1803
  node.type === 'TSTypeParameterDeclaration'
1803
1804
  ) {
1804
- if (node.loc) {
1805
+ if (has_location(node)) {
1805
1806
  const mapping = get_mapping_from_node(
1806
1807
  node,
1807
1808
  src_to_gen_map,
@@ -1,7 +1,8 @@
1
1
  /** @import * as AST from 'estree' */
2
2
 
3
3
  import * as b from '../utils/builders.js';
4
- import { clone_expression_node, clone_identifier } from './jsx/ast-builders.js';
4
+ import { is_function_or_class_node as is_function_or_class_boundary } from '../utils/ast.js';
5
+ import { clone_ast_node, clone_identifier } from './jsx/ast-builders.js';
5
6
 
6
7
  const regex_backslash_and_following_character = /\\(.)/g;
7
8
 
@@ -130,14 +131,10 @@ function create_style_ref_expression_statements(source, style_map, options) {
130
131
  options.allowMutableRefTarget !== false &&
131
132
  (source.type === 'Identifier' || source.type === 'MemberExpression')
132
133
  ) {
133
- const target = clone_expression_node(source, false);
134
+ const target = clone_ast_node(source, false);
134
135
  return [
135
136
  b.stmt(
136
- b.assignment(
137
- '=',
138
- /** @type {AST.Pattern} */ (target),
139
- clone_expression_node(style_map, false),
140
- ),
137
+ b.assignment('=', /** @type {AST.Pattern} */ (target), clone_ast_node(style_map, false)),
141
138
  ),
142
139
  ];
143
140
  }
@@ -146,8 +143,8 @@ function create_style_ref_expression_statements(source, style_map, options) {
146
143
  return [
147
144
  b.stmt(
148
145
  b.call(
149
- visit_expression(clone_expression_node(source, false), options),
150
- clone_expression_node(style_map, false),
146
+ visit_expression(clone_ast_node(source, false), options),
147
+ clone_ast_node(style_map, false),
151
148
  ),
152
149
  ),
153
150
  ];
@@ -166,17 +163,17 @@ function create_dynamic_style_ref_statement(source, style_map, options) {
166
163
  const ref_id = options.createTempIdentifier?.() ?? b.id('__tsrx_style_ref');
167
164
  const ref_read = () => clone_identifier(ref_id);
168
165
  const current_write = b.stmt(
169
- b.assignment('=', b.member(ref_read(), 'current'), clone_expression_node(style_map, false)),
166
+ b.assignment('=', b.member(ref_read(), 'current'), clone_ast_node(style_map, false)),
170
167
  );
171
168
  const value_write = b.stmt(
172
- b.assignment('=', b.member(ref_read(), 'value'), clone_expression_node(style_map, false)),
169
+ b.assignment('=', b.member(ref_read(), 'value'), clone_ast_node(style_map, false)),
173
170
  );
174
171
 
175
172
  return [
176
- b.let(ref_id, visit_expression(clone_expression_node(source, false), options)),
173
+ b.let(ref_id, visit_expression(clone_ast_node(source, false), options)),
177
174
  b.if(
178
175
  b.binary('===', b.unary('typeof', ref_read()), b.literal('function')),
179
- b.block([b.stmt(b.call(ref_read(), clone_expression_node(style_map, false)))]),
176
+ b.block([b.stmt(b.call(ref_read(), clone_ast_node(style_map, false)))]),
180
177
  b.if(
181
178
  b.logical(
182
179
  '&&',
@@ -236,20 +233,6 @@ function is_style_element(node) {
236
233
  return !!node && node.type === 'JSXStyleElement';
237
234
  }
238
235
 
239
- /**
240
- * @param {any} node
241
- * @returns {boolean}
242
- */
243
- function is_function_or_class_boundary(node) {
244
- return (
245
- node?.type === 'FunctionDeclaration' ||
246
- node?.type === 'FunctionExpression' ||
247
- node?.type === 'ArrowFunctionExpression' ||
248
- node?.type === 'ClassDeclaration' ||
249
- node?.type === 'ClassExpression'
250
- );
251
- }
252
-
253
236
  /**
254
237
  * @param {any} css
255
238
  * @returns {Map<string, any>}
package/src/utils/ast.js CHANGED
@@ -20,9 +20,20 @@
20
20
 
21
21
  import * as b from './builders.js';
22
22
 
23
+ /**
24
+ * @template {object} T
25
+ * @param {T | null | undefined} node
26
+ * @returns {node is T & AST.NodeWithLocation}
27
+ */
28
+ export function has_location(node) {
29
+ if (node == null) return false;
30
+ const location = /** @type {Partial<AST.NodeWithLocation>} */ (node);
31
+ return location.loc != null && location.start !== undefined && location.end !== undefined;
32
+ }
33
+
23
34
  /**
24
35
  * @param {AST.Node} node
25
- * @returns {boolean}
36
+ * @returns {node is AST.Function}
26
37
  */
27
38
  export function is_function_node(node) {
28
39
  return (
@@ -32,6 +43,14 @@ export function is_function_node(node) {
32
43
  );
33
44
  }
34
45
 
46
+ /**
47
+ * @param {AST.Node | null | undefined} node
48
+ * @returns {node is AST.Function | AST.ClassDeclaration | AST.ClassExpression}
49
+ */
50
+ export function is_function_or_class_node(node) {
51
+ return !!node && (is_function_node(node) || is_class_node(node));
52
+ }
53
+
35
54
  /**
36
55
  * @param {AST.Node} node
37
56
  * @returns {boolean}
@@ -48,6 +67,87 @@ export function is_class_node(node) {
48
67
  return node.type === 'ClassExpression' || node.type === 'ClassDeclaration';
49
68
  }
50
69
 
70
+ /**
71
+ * A parsed `@if`/`@for`/`@switch`/`@try` control-flow directive.
72
+ *
73
+ * @param {AST.Node | null | undefined} node
74
+ * @returns {node is AST.JSXTemplateDirective}
75
+ */
76
+ export function is_template_directive(node) {
77
+ return (
78
+ node?.type === 'JSXIfExpression' ||
79
+ node?.type === 'JSXForExpression' ||
80
+ node?.type === 'JSXSwitchExpression' ||
81
+ node?.type === 'JSXTryExpression'
82
+ );
83
+ }
84
+
85
+ /**
86
+ * Any source AST node that can be the rendered output of a TSRX template or
87
+ * statement container.
88
+ *
89
+ * @param {AST.Node | null | undefined} node
90
+ * @returns {node is AST.TSRXRenderOutput}
91
+ */
92
+ export function is_tsrx_render_output_node(node) {
93
+ return !!(
94
+ node &&
95
+ (node.type === 'JSXElement' ||
96
+ node.type === 'JSXFragment' ||
97
+ node.type === 'JSXStyleElement' ||
98
+ node.type === 'JSXCodeBlock' ||
99
+ is_template_directive(node))
100
+ );
101
+ }
102
+
103
+ /**
104
+ * @param {AST.Node | null | undefined} node
105
+ * @param {AST.Node | null | undefined} parent
106
+ * @returns {node is AST.JSXCodeBlock}
107
+ */
108
+ export function is_code_block_function_body(node, parent) {
109
+ return !!(
110
+ node &&
111
+ node.type === 'JSXCodeBlock' &&
112
+ parent &&
113
+ is_function_node(parent) &&
114
+ parent.body === node
115
+ );
116
+ }
117
+
118
+ /**
119
+ * Returns whether `child` directly occupies a statement slot of `parent`.
120
+ * Parser-native TSRX output nodes can appear in these slots without an
121
+ * `ExpressionStatement` wrapper, notably as braceless control-flow bodies.
122
+ *
123
+ * @param {AST.Node} parent
124
+ * @param {AST.Node} child
125
+ * @returns {boolean}
126
+ */
127
+ export function is_statement_position(parent, child) {
128
+ switch (parent.type) {
129
+ case 'Program':
130
+ case 'BlockStatement':
131
+ return parent.body.includes(/** @type {AST.Statement} */ (child));
132
+ case 'SwitchCase':
133
+ return parent.consequent.includes(/** @type {AST.Statement} */ (child));
134
+ case 'JSXCodeBlock':
135
+ return parent.body.includes(/** @type {AST.Statement} */ (child));
136
+ case 'IfStatement':
137
+ return parent.consequent === child || parent.alternate === child;
138
+ case 'ForStatement':
139
+ case 'ForInStatement':
140
+ case 'ForOfStatement':
141
+ case 'WhileStatement':
142
+ case 'DoWhileStatement':
143
+ case 'LabeledStatement':
144
+ case 'WithStatement':
145
+ return parent.body === child;
146
+ default:
147
+ return false;
148
+ }
149
+ }
150
+
51
151
  /**
52
152
  * Returns the closest native TSRX function in an ancestry path. By default,
53
153
  * function and class boundaries stop the search so callers only match direct
@@ -1121,7 +1121,7 @@ export function key(name) {
1121
1121
 
1122
1122
  /**
1123
1123
  * @param {ESTreeJSX.JSXIdentifier | ESTreeJSX.JSXNamespacedName} name
1124
- * @param {AST.Literal | ESTreeJSX.JSXExpressionContainer | null} value
1124
+ * @param {ESTreeJSX.JSXAttribute['value']} value
1125
1125
  * @param {boolean} [shorthand]
1126
1126
  * @param {AST.NodeWithLocation} [loc_info]
1127
1127
  * @returns {ESTreeJSX.JSXAttribute}
@@ -1142,7 +1142,7 @@ export function jsx_attribute(name, value = null, shorthand = false, loc_info) {
1142
1142
  * Build a fresh `JSXOpeningElement`. For elements derived from an existing
1143
1143
  * JSX element node, prefer `jsx_element` which spreads from the source.
1144
1144
  *
1145
- * @param {ESTreeJSX.JSXOpeningElement['name']} name
1145
+ * @param {ESTreeJSX.TSRXJSXOpeningElement['name']} name
1146
1146
  * @param {ESTreeJSX.JSXOpeningElement['attributes']} [attributes]
1147
1147
  * @param {boolean} [self_closing]
1148
1148
  * @param {ESTreeJSX.JSXOpeningElement['typeArguments']} [type_arguments]
@@ -1171,7 +1171,7 @@ export function jsx_opening_element(
1171
1171
  /**
1172
1172
  * Build a fresh `JSXClosingElement`.
1173
1173
  *
1174
- * @param {ESTreeJSX.JSXClosingElement['name']} name
1174
+ * @param {ESTreeJSX.TSRXJSXClosingElement['name']} name
1175
1175
  * @param {AST.NodeWithLocation} [loc_info]
1176
1176
  * @returns {ESTreeJSX.JSXClosingElement}
1177
1177
  */