@tsrx/core 0.1.15 → 0.1.16

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/plugin.js +75 -0
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.15",
6
+ "version": "0.1.16",
7
7
  "type": "module",
8
8
  "repository": {
9
9
  "type": "git",
package/src/plugin.js CHANGED
@@ -21,6 +21,10 @@ const JSX_EXPRESSION_VALUE_ERROR =
21
21
  'JSX elements cannot be used as expressions. Wrap JSX with `<>...</>` or `<tsx>...</tsx>`, wrap TSRX templates with `<tsrx>...</tsrx>`, or use elements as statements within a component.';
22
22
  const HTML_ATTRIBUTE_VALUE_ERROR =
23
23
  '`{html ...}` is not supported as an attribute value. Use a string literal or expression without `html`.';
24
+ const DYNAMIC_ELEMENT_IN_TSX_ERROR =
25
+ 'Dynamic element syntax (`<@...>`) is only supported in native TSRX templates.';
26
+ const DYNAMIC_ATTRIBUTE_NAME_ERROR =
27
+ 'Dynamic component / element syntax (`@`) is only supported on native TSRX element names, not attribute names.';
24
28
 
25
29
  const CharCode = Object.freeze({
26
30
  tab: 9,
@@ -408,6 +412,38 @@ export function TSRXPlugin(config) {
408
412
  );
409
413
  }
410
414
 
415
+ /**
416
+ * @param {AST.Node[]} children
417
+ */
418
+ #reportDynamicJsxElementsInTsx(children) {
419
+ for (const child of children) {
420
+ if (child?.type === 'Tsrx') {
421
+ continue;
422
+ }
423
+ if (child?.type === 'JSXElement') {
424
+ const name = child.openingElement?.name;
425
+ if (name?.type === 'JSXIdentifier' && name.name === 'tsrx') {
426
+ continue;
427
+ }
428
+ const is_dynamic_name =
429
+ (name?.type === 'JSXIdentifier' && name.tracked) ||
430
+ (name?.type === 'JSXMemberExpression' &&
431
+ name.object.type === 'JSXIdentifier' &&
432
+ name.object.tracked);
433
+ if (is_dynamic_name) {
434
+ this.#report_recoverable_error_range(
435
+ /** @type {AST.NodeWithLocation} */ (name).start ?? child.start,
436
+ /** @type {AST.NodeWithLocation} */ (name).end ?? child.end,
437
+ DYNAMIC_ELEMENT_IN_TSX_ERROR,
438
+ );
439
+ }
440
+ this.#reportDynamicJsxElementsInTsx(/** @type {AST.Node[]} */ (child.children));
441
+ } else if (child?.type === 'Tsx' || child?.type === 'TsxCompat') {
442
+ this.#reportDynamicJsxElementsInTsx(/** @type {AST.Node[]} */ (child.children));
443
+ }
444
+ }
445
+ }
446
+
411
447
  #parseNativeTemplateExpressionContainer() {
412
448
  const allow_trailing_semicolon = this.#allowExpressionContainerTrailingSemicolon;
413
449
  this.#allowExpressionContainerTrailingSemicolon = true;
@@ -2107,6 +2143,20 @@ export function TSRXPlugin(config) {
2107
2143
  }
2108
2144
  }
2109
2145
  /** @type {ESTreeJSX.JSXAttribute} */ (node).name = this.jsx_parseNamespacedName();
2146
+ if (
2147
+ /** @type {ESTreeJSX.JSXAttribute} */ (node).name.type === 'JSXIdentifier' &&
2148
+ /** @type {ESTreeJSX.JSXIdentifier} */ (/** @type {ESTreeJSX.JSXAttribute} */ (node).name)
2149
+ .tracked
2150
+ ) {
2151
+ this.#report_recoverable_error_range(
2152
+ /** @type {AST.NodeWithLocation} */ (node).start,
2153
+ /** @type {AST.NodeWithLocation} */ (/** @type {ESTreeJSX.JSXAttribute} */ (node).name)
2154
+ .end ??
2155
+ node.end ??
2156
+ node.start,
2157
+ DYNAMIC_ATTRIBUTE_NAME_ERROR,
2158
+ );
2159
+ }
2110
2160
  const value = /** @type {ESTreeJSX.JSXAttribute['value'] | null} */ (
2111
2161
  this.eat(tt.eq) ? this.jsx_parseAttributeValue() : null
2112
2162
  );
@@ -2512,6 +2562,13 @@ export function TSRXPlugin(config) {
2512
2562
  (current_template_node?.type === 'TsxCompat' || current_template_node?.type === 'Tsx') &&
2513
2563
  !is_tsrx_tag
2514
2564
  ) {
2565
+ if (this.input.charCodeAt(tag_name_start) === CharCode.at) {
2566
+ this.#report_recoverable_error_range(
2567
+ this.start,
2568
+ tag_name_start + 1,
2569
+ DYNAMIC_ELEMENT_IN_TSX_ERROR,
2570
+ );
2571
+ }
2515
2572
  // Inside tsx/tsx:*, let acorn-jsx handle regular TSX tags normally.
2516
2573
  // Nested <tsrx> still needs Ripple's native template parser so it
2517
2574
  // can lower through the same path as <tsrx> in component bodies.
@@ -2603,6 +2660,12 @@ export function TSRXPlugin(config) {
2603
2660
  !is_tsx_compat &&
2604
2661
  open.name.type === 'JSXIdentifier' &&
2605
2662
  open.name.name === 'tsrx';
2663
+ const is_dynamic_name =
2664
+ !is_fragment &&
2665
+ ((open.name.type === 'JSXIdentifier' && open.name.tracked) ||
2666
+ (open.name.type === 'JSXMemberExpression' &&
2667
+ open.name.object.type === 'JSXIdentifier' &&
2668
+ open.name.object.tracked));
2606
2669
 
2607
2670
  if (is_tsx_compat) {
2608
2671
  const namespace_node = /** @type {ESTreeJSX.JSXNamespacedName} */ (open.name);
@@ -2640,6 +2703,14 @@ export function TSRXPlugin(config) {
2640
2703
  element.type = 'Element';
2641
2704
  }
2642
2705
 
2706
+ if ((is_tsx || is_fragment) && is_dynamic_name) {
2707
+ this.#report_recoverable_error_range(
2708
+ open.name.start ?? open.start,
2709
+ open.name.end ?? open.end,
2710
+ DYNAMIC_ELEMENT_IN_TSX_ERROR,
2711
+ );
2712
+ }
2713
+
2643
2714
  for (const attr of open.attributes) {
2644
2715
  if (attr.type === 'JSXAttribute') {
2645
2716
  /** @type {AST.Attribute} */ (/** @type {unknown} */ (attr)).type = 'Attribute';
@@ -2698,6 +2769,7 @@ export function TSRXPlugin(config) {
2698
2769
  this.#parseNativeTemplateBody(element, /** @type {AST.Element} */ (element).children, {
2699
2770
  enterScope: true,
2700
2771
  });
2772
+ this.#reportDynamicJsxElementsInTsx(/** @type {AST.Element} */ (element).children);
2701
2773
 
2702
2774
  if (/** @type {AST.Tsx} */ (element).type === 'Tsx') {
2703
2775
  this.#path.pop();
@@ -2875,6 +2947,9 @@ export function TSRXPlugin(config) {
2875
2947
  this.#parseNativeTemplateBody(element, /** @type {AST.Element} */ (element).children, {
2876
2948
  enterScope: true,
2877
2949
  });
2950
+ if (/** @type {AST.Tsx} */ (element).type === 'Tsx') {
2951
+ this.#reportDynamicJsxElementsInTsx(/** @type {AST.Element} */ (element).children);
2952
+ }
2878
2953
 
2879
2954
  if (/** @type {AST.Tsx} */ (element).type === 'Tsx') {
2880
2955
  this.#path.pop();