@tsrx/core 0.1.56 → 0.1.57

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.56",
6
+ "version": "0.1.57",
7
7
  "type": "module",
8
8
  "repository": {
9
9
  "type": "git",
package/src/plugin.js CHANGED
@@ -108,6 +108,28 @@ function get_argument_clash_reported_names(check_clashes) {
108
108
  return reported_names;
109
109
  }
110
110
 
111
+ /**
112
+ * A `<` opens a tag only when the character after it can begin one: `/` for a
113
+ * closing tag, `>` for a fragment, `{` for a dynamic tag, or an element or
114
+ * component name start. Any other character, or end of input, leaves the `<`
115
+ * as literal text.
116
+ * @param {string} input
117
+ * @param {number} index Index of the `<`.
118
+ */
119
+ function can_start_tag_after_lt(input, index) {
120
+ const next = index + 1 < input.length ? input.charCodeAt(index + 1) : -1;
121
+ return (
122
+ next === CharCode.slash ||
123
+ next === CharCode.greaterThan ||
124
+ next === CharCode.openBrace ||
125
+ next === CharCode.at ||
126
+ next === CharCode.dollar ||
127
+ next === CharCode.underscore ||
128
+ (next >= CharCode.uppercaseA && next <= CharCode.uppercaseZ) ||
129
+ (next >= CharCode.lowercaseA && next <= CharCode.lowercaseZ)
130
+ );
131
+ }
132
+
111
133
  /**
112
134
  * @param {string} input
113
135
  * @param {number} i
@@ -623,7 +645,7 @@ export function TSRXPlugin(config) {
623
645
  }
624
646
  const ch = this.input.charCodeAt(index);
625
647
  if (
626
- ch === CharCode.lessThan ||
648
+ (ch === CharCode.lessThan && can_start_tag_after_lt(this.input, index)) ||
627
649
  ch === CharCode.openBrace ||
628
650
  ch === CharCode.closeBrace ||
629
651
  this.#isCodeBlockStart(index) ||
@@ -1146,7 +1168,7 @@ export function TSRXPlugin(config) {
1146
1168
  while (index < this.input.length) {
1147
1169
  const ch = this.input.charCodeAt(index);
1148
1170
  if (
1149
- ch === CharCode.lessThan ||
1171
+ (ch === CharCode.lessThan && can_start_tag_after_lt(this.input, index)) ||
1150
1172
  ch === CharCode.openBrace ||
1151
1173
  ch === CharCode.closeBrace ||
1152
1174
  this.#isJSXControlFlowDirectiveAt(index) ||
@@ -1247,16 +1269,8 @@ export function TSRXPlugin(config) {
1247
1269
  const index = skip_whitespace_from(this.input, this.start);
1248
1270
  const ch = this.input.charCodeAt(index);
1249
1271
  if (ch === CharCode.lessThan) {
1250
- const next = this.input.charCodeAt(index + 1);
1251
- if (next === CharCode.slash) return false;
1252
- const tagLike =
1253
- next === CharCode.greaterThan ||
1254
- next === CharCode.openBrace ||
1255
- next === CharCode.at ||
1256
- next === CharCode.dollar ||
1257
- next === CharCode.underscore ||
1258
- (next >= CharCode.uppercaseA && next <= CharCode.uppercaseZ) ||
1259
- (next >= CharCode.lowercaseA && next <= CharCode.lowercaseZ);
1272
+ if (this.input.charCodeAt(index + 1) === CharCode.slash) return false;
1273
+ const tagLike = can_start_tag_after_lt(this.input, index);
1260
1274
  const previous = this.#previousNonSpaceTabIndex(index);
1261
1275
  const afterSemicolon =
1262
1276
  previous >= 0 && this.input.charCodeAt(previous) === CharCode.semicolon;
@@ -2895,7 +2909,7 @@ export function TSRXPlugin(config) {
2895
2909
  this.#suppressTemplateRawTextToken = false;
2896
2910
  const context = this.curContext();
2897
2911
  if (
2898
- code !== CharCode.lessThan &&
2912
+ (code !== CharCode.lessThan || !can_start_tag_after_lt(this.input, this.pos)) &&
2899
2913
  code !== CharCode.greaterThan &&
2900
2914
  code !== CharCode.openBrace &&
2901
2915
  code !== CharCode.closeBrace &&
@@ -2933,17 +2947,7 @@ export function TSRXPlugin(config) {
2933
2947
  return super.readToken(code);
2934
2948
  }
2935
2949
  if (code === CharCode.lessThan) {
2936
- const next = this.input.charCodeAt(this.pos + 1);
2937
- const isTagLikeAfterLt =
2938
- next === CharCode.slash ||
2939
- next === CharCode.greaterThan ||
2940
- next === CharCode.openBrace ||
2941
- next === CharCode.at ||
2942
- next === CharCode.dollar ||
2943
- next === CharCode.underscore ||
2944
- (next >= CharCode.uppercaseA && next <= CharCode.uppercaseZ) ||
2945
- (next >= CharCode.lowercaseA && next <= CharCode.lowercaseZ);
2946
- if (this.exprAllowed && isTagLikeAfterLt) {
2950
+ if (this.exprAllowed && can_start_tag_after_lt(this.input, this.pos)) {
2947
2951
  ++this.pos;
2948
2952
  return this.finishToken(tstt.jsxTagStart);
2949
2953
  }
@@ -3058,21 +3062,7 @@ export function TSRXPlugin(config) {
3058
3062
  // <Something>...</Something>\n\n<Child />
3059
3063
  // <head><style>...</style></head>
3060
3064
  // We only do this when '<' is in a tag-like position.
3061
- const isWhitespaceAfterLt =
3062
- nextChar === CharCode.space ||
3063
- nextChar === CharCode.tab ||
3064
- nextChar === CharCode.lineFeed ||
3065
- nextChar === CharCode.carriageReturn;
3066
- const isTagLikeAfterLt =
3067
- !isWhitespaceAfterLt &&
3068
- (nextChar === CharCode.slash ||
3069
- nextChar === CharCode.greaterThan ||
3070
- nextChar === CharCode.openBrace ||
3071
- nextChar === CharCode.at ||
3072
- nextChar === CharCode.dollar ||
3073
- nextChar === CharCode.underscore ||
3074
- (nextChar >= CharCode.uppercaseA && nextChar <= CharCode.uppercaseZ) ||
3075
- (nextChar >= CharCode.lowercaseA && nextChar <= CharCode.lowercaseZ));
3065
+ const isTagLikeAfterLt = can_start_tag_after_lt(this.input, this.pos);
3076
3066
  const prevAllowsTagStart =
3077
3067
  prevNonWhitespaceChar === null ||
3078
3068
  prevNonWhitespaceChar === CharCode.lineFeed || // '\n'
@@ -3099,7 +3089,7 @@ export function TSRXPlugin(config) {
3099
3089
  prevNonWhitespaceChar === CharCode.openBrace ||
3100
3090
  prevNonWhitespaceChar === CharCode.greaterThan
3101
3091
  ) {
3102
- if (!isWhitespaceAfterLt) {
3092
+ if (isTagLikeAfterLt) {
3103
3093
  ++this.pos;
3104
3094
  return this.finishToken(tstt.jsxTagStart);
3105
3095
  }
@@ -4136,6 +4126,16 @@ export function TSRXPlugin(config) {
4136
4126
 
4137
4127
  case CharCode.lessThan:
4138
4128
  case CharCode.openBrace:
4129
+ // This path reads the children of an element nested in a `{ … }`
4130
+ // expression container, where the raw-text intercept in `readToken`
4131
+ // is off, so the literal-`<` rule has to be applied here too. Keep
4132
+ // scanning rather than finishing the token: the `<` belongs to the
4133
+ // text run, and finishing here would emit an empty text token when
4134
+ // it is the run's first character.
4135
+ if (ch === CharCode.lessThan && !can_start_tag_after_lt(this.input, this.pos)) {
4136
+ ++this.pos;
4137
+ break;
4138
+ }
4139
4139
  if (out || this.pos > chunkStart) {
4140
4140
  return this.finishToken(tstt.jsxText, out + this.input.slice(chunkStart, this.pos));
4141
4141
  }
@@ -4629,10 +4629,13 @@ export function TSRXPlugin(config) {
4629
4629
  } else if (this.type === tstt.jsxText) {
4630
4630
  // A nested element with its own body can leak a JSX expression context,
4631
4631
  // so the whitespace after its closing tag is mis-tokenized as a stale
4632
- // text token whose start was advanced onto the following `<`. Real JSX
4633
- // text never starts at `<`, so drop the leaked context and re-read the
4634
- // tag instead of emitting an empty node.
4635
- if (this.input.charCodeAt(this.start) === CharCode.lessThan) {
4632
+ // text token whose start was advanced onto the following `<`. Text never
4633
+ // starts at a `<` that can open a tag, so drop the leaked context and
4634
+ // re-read the tag instead of emitting an empty node.
4635
+ if (
4636
+ this.input.charCodeAt(this.start) === CharCode.lessThan &&
4637
+ can_start_tag_after_lt(this.input, this.start)
4638
+ ) {
4636
4639
  if (this.#jsxExpressionContainerDepth > 0) {
4637
4640
  // Inside a `{ … }` container the whole-stack counts below are
4638
4641
  // blind: the enclosing template's `tc_expr` contexts sit on the
@@ -152,6 +152,15 @@ export function tsx_with_ts_locations(boundary_tokens = false, comments = undefi
152
152
  context.visit(value.body);
153
153
  },
154
154
 
155
+ // TSRX text may contain a literal `<` — one that cannot start a tag
156
+ // (`<span><3</span>`) or a raw-text `<script>` body — but the printed TSX
157
+ // is re-parsed by a JSX toolchain (esbuild, Babel, SWC), and JSX forbids
158
+ // a bare `<` in text. Emit it as `&lt;`, which those parsers decode back
159
+ // to the same string.
160
+ JSXText: (node, context) => {
161
+ context.write(node.value.replace(/</g, '&lt;'), node);
162
+ },
163
+
155
164
  // esrap's JSXOpeningElement printer doesn't emit `typeArguments`, so generic
156
165
  // component tags like `<RenderProp<User>>` lose the `<User>` in the output.
157
166
  JSXOpeningElement: (node, context) => {