@tsrx/core 0.1.24 → 0.1.25

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.24",
6
+ "version": "0.1.25",
7
7
  "type": "module",
8
8
  "repository": {
9
9
  "type": "git",
package/src/plugin.js CHANGED
@@ -4232,7 +4232,36 @@ export function TSRXPlugin(config) {
4232
4232
  body.push(text);
4233
4233
  }
4234
4234
  } else if (this.#isJSXControlFlowDirectiveStart()) {
4235
- body.push(this.#parseJSXControlFlowExpression());
4235
+ const directive = this.#parseJSXControlFlowExpression();
4236
+ body.push(directive);
4237
+ // `#parseTemplateControlFlowBlock` reads the token after the block's
4238
+ // closing `}` in a code (b_stat) context, which runs `skipSpace()` and
4239
+ // advances `start` past any whitespace. The following token is therefore a
4240
+ // JS token (e.g. the `else` keyword), and when it is actually sibling
4241
+ // template raw text it reaches `#parseTemplateRawText` having lost the
4242
+ // space(s) between `}` and the text (e.g. `@if (x) { … } else` -> the text
4243
+ // "else" instead of " else"). JSX text after a plain element keeps that
4244
+ // whitespace, so when raw text follows and only whitespace was skipped,
4245
+ // rewind `start` to the block's end to re-include the dropped whitespace.
4246
+ const blockEnd = directive.end;
4247
+ const nextCh = this.input.charCodeAt(this.start);
4248
+ const startsRawText =
4249
+ this.type !== tt.eof &&
4250
+ nextCh !== CharCode.lessThan &&
4251
+ nextCh !== CharCode.openBrace &&
4252
+ nextCh !== CharCode.closeBrace &&
4253
+ !this.#isJSXControlFlowDirectiveStart();
4254
+ if (
4255
+ startsRawText &&
4256
+ typeof blockEnd === 'number' &&
4257
+ this.start > blockEnd &&
4258
+ /^\s*$/.test(this.input.slice(blockEnd, this.start))
4259
+ ) {
4260
+ const loc = acorn.getLineInfo(this.input, blockEnd);
4261
+ this.pos = blockEnd;
4262
+ this.start = blockEnd;
4263
+ this.startLoc = new acorn.Position(loc.line, loc.column);
4264
+ }
4236
4265
  } else if (this.type === tt.braceR) {
4237
4266
  // Leaving a native template body. We may still be in TSX/JSX tokenization
4238
4267
  // context (e.g. after parsing markup), but the closing `}` is a JS token.
@@ -4259,6 +4288,18 @@ export function TSRXPlugin(config) {
4259
4288
  this.start = startPos;
4260
4289
  this.startLoc = startLoc;
4261
4290
  this.exprAllowed = false;
4291
+ // A genuine `jsxTagStart` pushes `tc_expr` + `tc_oTag` in its
4292
+ // `updateContext`; faking the token here skips those pushes. That is
4293
+ // harmless for an opening tag (the next token is the tag name), but a
4294
+ // closing tag (`</`) immediately runs `context.length -= 2` in the
4295
+ // slash `updateContext`, which would underflow the context stack and
4296
+ // throw "Invalid array length" (e.g. `<>@if (a) { … } done</>`). Push
4297
+ // the two contexts a real `jsxTagStart` would have added so the closing
4298
+ // tag pops its own contexts instead of the enclosing template's.
4299
+ if (this.input.charCodeAt(this.pos) === CharCode.slash) {
4300
+ this.context.push(tstc.tc_expr);
4301
+ this.context.push(tstc.tc_oTag);
4302
+ }
4262
4303
  this.next();
4263
4304
  }
4264
4305
  if (this.value === '/' || this.type === tt.slash) {