@tsrx/core 0.1.2 → 0.1.4
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 +1 -1
- package/src/index.js +1 -0
- package/src/plugin.js +423 -234
- package/src/transform/jsx/index.js +1166 -108
package/src/plugin.js
CHANGED
|
@@ -216,10 +216,13 @@ export function TSRXPlugin(config) {
|
|
|
216
216
|
#loose = false;
|
|
217
217
|
/** @type {AST.Node[]} */
|
|
218
218
|
#functionStack = [];
|
|
219
|
+
/** @type {Array<{ parentContext: any[], canRestore: boolean, restore: boolean }>} */
|
|
220
|
+
#functionBodyContextRestoreStack = [];
|
|
219
221
|
/** @type {import('../types/index').CompileError[] | undefined} */
|
|
220
222
|
#errors = undefined;
|
|
221
223
|
/** @type {string | null} */
|
|
222
224
|
#filename = null;
|
|
225
|
+
#componentDepth = 0;
|
|
223
226
|
#functionBodyDepth = 0;
|
|
224
227
|
|
|
225
228
|
/**
|
|
@@ -270,6 +273,212 @@ export function TSRXPlugin(config) {
|
|
|
270
273
|
return null;
|
|
271
274
|
}
|
|
272
275
|
|
|
276
|
+
#isInsideComponent() {
|
|
277
|
+
return this.#componentDepth > 0;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
#isInsideComponentTemplate() {
|
|
281
|
+
return this.#isInsideComponent() && this.#functionBodyDepth === 0;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Component bodies and native TSRX element bodies share the same grammar.
|
|
286
|
+
* This helper keeps the parser-state setup in one place while callers keep
|
|
287
|
+
* ownership of their distinct closing delimiter handling (`}` vs `</tag>`).
|
|
288
|
+
*
|
|
289
|
+
* @param {AST.Node} node
|
|
290
|
+
* @param {AST.Node[]} body
|
|
291
|
+
* @param {{
|
|
292
|
+
* enterScope?: boolean,
|
|
293
|
+
* pushPath?: boolean,
|
|
294
|
+
* trackComponentDepth?: boolean,
|
|
295
|
+
* resetFunctionBodyDepth?: boolean,
|
|
296
|
+
* }} [options]
|
|
297
|
+
*/
|
|
298
|
+
#parseNativeTemplateBody(
|
|
299
|
+
node,
|
|
300
|
+
body,
|
|
301
|
+
{
|
|
302
|
+
enterScope = false,
|
|
303
|
+
pushPath = false,
|
|
304
|
+
trackComponentDepth = false,
|
|
305
|
+
resetFunctionBodyDepth = false,
|
|
306
|
+
} = {},
|
|
307
|
+
) {
|
|
308
|
+
const parent_function_body_depth = this.#functionBodyDepth;
|
|
309
|
+
|
|
310
|
+
if (resetFunctionBodyDepth) {
|
|
311
|
+
this.#functionBodyDepth = 0;
|
|
312
|
+
}
|
|
313
|
+
if (enterScope) {
|
|
314
|
+
this.enterScope(0);
|
|
315
|
+
}
|
|
316
|
+
if (pushPath) {
|
|
317
|
+
this.#path.push(node);
|
|
318
|
+
}
|
|
319
|
+
if (trackComponentDepth) {
|
|
320
|
+
this.#componentDepth++;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
this.parseTemplateBody(body);
|
|
325
|
+
} finally {
|
|
326
|
+
if (trackComponentDepth) {
|
|
327
|
+
this.#componentDepth--;
|
|
328
|
+
}
|
|
329
|
+
if (pushPath) {
|
|
330
|
+
this.#path.pop();
|
|
331
|
+
}
|
|
332
|
+
if (enterScope) {
|
|
333
|
+
this.exitScope();
|
|
334
|
+
}
|
|
335
|
+
if (resetFunctionBodyDepth) {
|
|
336
|
+
this.#functionBodyDepth = parent_function_body_depth;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* @param {AST.Node | undefined} node
|
|
343
|
+
*/
|
|
344
|
+
#isNativeTemplateNode(node) {
|
|
345
|
+
return (
|
|
346
|
+
node?.type === 'Component' ||
|
|
347
|
+
node?.type === 'Element' ||
|
|
348
|
+
node?.type === 'Tsx' ||
|
|
349
|
+
node?.type === 'Tsrx' ||
|
|
350
|
+
node?.type === 'TsxCompat'
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
#parseNativeTemplateExpressionContainer() {
|
|
355
|
+
const node = this.jsx_parseExpressionContainer();
|
|
356
|
+
// Keep JSXEmptyExpression as-is (for prettier to handle comments)
|
|
357
|
+
// but convert other expressions to native TSRX child nodes.
|
|
358
|
+
if (node.expression.type !== 'JSXEmptyExpression') {
|
|
359
|
+
/** @type {AST.TSRXExpression | AST.Html | AST.TextNode | AST.Style} */ (
|
|
360
|
+
/** @type {unknown} */ (node)
|
|
361
|
+
).type = node.html
|
|
362
|
+
? 'Html'
|
|
363
|
+
: node.text
|
|
364
|
+
? 'Text'
|
|
365
|
+
: node.style
|
|
366
|
+
? 'Style'
|
|
367
|
+
: 'TSRXExpression';
|
|
368
|
+
if (node.style) {
|
|
369
|
+
/** @type {AST.Style} */ (/** @type {unknown} */ (node)).value =
|
|
370
|
+
/** @type {AST.Literal} */ (node.expression);
|
|
371
|
+
delete (/** @type {any} */ (node).expression);
|
|
372
|
+
}
|
|
373
|
+
delete node.html;
|
|
374
|
+
delete node.text;
|
|
375
|
+
delete node.style;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return /** @type {ESTreeJSX.JSXEmptyExpression | AST.TSRXExpression | AST.Html | AST.TextNode | AST.Style | ESTreeJSX.JSXExpressionContainer} */ (
|
|
379
|
+
/** @type {unknown} */ (node)
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* @param {AST.Tsx | AST.TsxCompat} island
|
|
385
|
+
* @param {AST.Node[]} body
|
|
386
|
+
*/
|
|
387
|
+
#parseTsxIslandBody(island, body) {
|
|
388
|
+
const tagName =
|
|
389
|
+
island.type === 'TsxCompat'
|
|
390
|
+
? `tsx:${island.kind}`
|
|
391
|
+
: island.openingElement.name
|
|
392
|
+
? 'tsx'
|
|
393
|
+
: '';
|
|
394
|
+
|
|
395
|
+
this.exprAllowed = true;
|
|
396
|
+
|
|
397
|
+
while (true) {
|
|
398
|
+
if (this.type === tt.eof || this.pos >= this.input.length || this.type === tt.braceR) {
|
|
399
|
+
const displayTag = tagName || '';
|
|
400
|
+
this.#report_broken_markup_error(
|
|
401
|
+
this.start,
|
|
402
|
+
`Unclosed tag '<${displayTag}>'. Expected '</${displayTag}>' before end of component.`,
|
|
403
|
+
);
|
|
404
|
+
island.unclosed = true;
|
|
405
|
+
/** @type {AST.NodeWithLocation} */ (island).loc.end = {
|
|
406
|
+
.../** @type {AST.SourceLocation} */ (island.openingElement.loc).end,
|
|
407
|
+
};
|
|
408
|
+
island.end = island.openingElement.end;
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (this.#isAtTsxIslandClosing(island)) {
|
|
413
|
+
this.exprAllowed = false;
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (this.type === tt.braceL) {
|
|
418
|
+
body.push(this.jsx_parseExpressionContainer());
|
|
419
|
+
} else if (this.type === tstt.jsxTagStart) {
|
|
420
|
+
body.push(super.jsx_parseElement());
|
|
421
|
+
} else {
|
|
422
|
+
const node = this.#parseTsxIslandText();
|
|
423
|
+
if (node) {
|
|
424
|
+
body.push(node);
|
|
425
|
+
}
|
|
426
|
+
this.#popTemplateLiteralTokenContext();
|
|
427
|
+
this.next();
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* @param {AST.Tsx | AST.TsxCompat} island
|
|
434
|
+
*/
|
|
435
|
+
#isAtTsxIslandClosing(island) {
|
|
436
|
+
if (island.type === 'TsxCompat') {
|
|
437
|
+
return this.input.slice(this.pos, this.pos + 5) === '/tsx:';
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (!island.openingElement.name) {
|
|
441
|
+
return this.input.slice(this.pos, this.pos + 2) === '/>';
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (this.input.slice(this.pos, this.pos + 4) !== '/tsx') {
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const after = this.input.charCodeAt(this.pos + 4);
|
|
449
|
+
return after === 62 /* > */;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
#parseTsxIslandText() {
|
|
453
|
+
const start = this.start;
|
|
454
|
+
this.pos = start;
|
|
455
|
+
let text = '';
|
|
456
|
+
|
|
457
|
+
while (this.pos < this.input.length) {
|
|
458
|
+
const ch = this.input.charCodeAt(this.pos);
|
|
459
|
+
|
|
460
|
+
// Stop at opening tag, expression, or the component-closing brace
|
|
461
|
+
if (ch === 60 || ch === 123 || ch === 125) {
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
text += this.input[this.pos];
|
|
466
|
+
this.pos++;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (!text) {
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return /** @type {ESTreeJSX.JSXText} */ ({
|
|
474
|
+
type: 'JSXText',
|
|
475
|
+
value: text,
|
|
476
|
+
raw: text,
|
|
477
|
+
start,
|
|
478
|
+
end: this.pos,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
273
482
|
#popTsxTokenContextBeforeTemplateExpressionChild() {
|
|
274
483
|
let index = this.pos;
|
|
275
484
|
let has_newline = false;
|
|
@@ -320,17 +529,133 @@ export function TSRXPlugin(config) {
|
|
|
320
529
|
}
|
|
321
530
|
}
|
|
322
531
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
532
|
+
/**
|
|
533
|
+
* @param {number} index
|
|
534
|
+
* @returns {number}
|
|
535
|
+
*/
|
|
536
|
+
#skipWhitespaceAndComments(index) {
|
|
537
|
+
while (index < this.input.length) {
|
|
538
|
+
const ch = this.input.charCodeAt(index);
|
|
539
|
+
if (ch === 32 || ch === 9 || ch === 10 || ch === 13) {
|
|
540
|
+
index++;
|
|
541
|
+
} else if (ch === 47 && this.input.charCodeAt(index + 1) === 42) {
|
|
542
|
+
const end = this.input.indexOf('*/', index + 2);
|
|
543
|
+
index = end === -1 ? this.input.length : end + 2;
|
|
544
|
+
} else if (ch === 47 && this.input.charCodeAt(index + 1) === 47) {
|
|
545
|
+
index += 2;
|
|
546
|
+
while (index < this.input.length) {
|
|
547
|
+
const comment_ch = this.input.charCodeAt(index);
|
|
548
|
+
if (comment_ch === 10 || comment_ch === 13) break;
|
|
549
|
+
index++;
|
|
550
|
+
}
|
|
551
|
+
} else {
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return index;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/** @returns {number} */
|
|
559
|
+
#countFollowingRightBraces() {
|
|
560
|
+
let index = this.end;
|
|
561
|
+
let count = 0;
|
|
562
|
+
while (index < this.input.length) {
|
|
563
|
+
index = this.#skipWhitespaceAndComments(index);
|
|
564
|
+
if (this.input.charCodeAt(index) !== 125) break;
|
|
565
|
+
count++;
|
|
566
|
+
index++;
|
|
326
567
|
}
|
|
568
|
+
return count;
|
|
569
|
+
}
|
|
327
570
|
|
|
328
|
-
|
|
571
|
+
/**
|
|
572
|
+
* @param {AST.Tsx | AST.Tsrx | AST.TsxCompat} node
|
|
573
|
+
* @returns {boolean}
|
|
574
|
+
*/
|
|
575
|
+
#hasDirectStatementChild(node) {
|
|
576
|
+
return node.children?.some(
|
|
577
|
+
(child) => child.type.endsWith('Statement') || child.type === 'VariableDeclaration',
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* @param {AST.Tsx | AST.Tsrx | AST.TsxCompat} node
|
|
583
|
+
*/
|
|
584
|
+
#popTokenContextsAfterTemplateExpressionElement(node) {
|
|
585
|
+
const ctx = this.context;
|
|
586
|
+
const ci = ctx.length - 1;
|
|
587
|
+
const top = ctx[ci];
|
|
588
|
+
const second = ctx[ci - 1];
|
|
589
|
+
|
|
590
|
+
// Expression-bodied templates (no statement child) followed by `,`
|
|
591
|
+
// in an object/array literal need surgical fixups; statement-bodied
|
|
592
|
+
// templates fall through to the JSX-expression-container strip.
|
|
593
|
+
const has_stmt_child = this.#hasDirectStatementChild(node);
|
|
594
|
+
if (this.type === tt.comma && !has_stmt_child) {
|
|
595
|
+
// Tail `..., (b_expr)+, tc_expr, b_stat`: the JSX expression
|
|
596
|
+
// container leaks an extra `tc_expr, b_stat`. Pop them, and if
|
|
597
|
+
// the JSX container also closes immediately (`}}` ahead), drop
|
|
598
|
+
// one of the doubled-up `b_expr` contexts too.
|
|
599
|
+
if (top === b_stat && second === tstc.tc_expr) {
|
|
600
|
+
let expr_count = 0;
|
|
601
|
+
for (let i = ci - 2; ctx[i] === b_expr; i--) expr_count++;
|
|
602
|
+
const following_braces = this.#countFollowingRightBraces();
|
|
603
|
+
if (expr_count === 2 || following_braces > 1) {
|
|
604
|
+
if (following_braces > 1 && expr_count > 1) {
|
|
605
|
+
ctx.splice(ci - 2, expr_count - 1);
|
|
606
|
+
ctx.pop();
|
|
607
|
+
this.exprAllowed = false;
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
if (expr_count === 2 && following_braces === 0) {
|
|
611
|
+
// Fragment expression value followed by another
|
|
612
|
+
// object/array entry inside a JSX expression
|
|
613
|
+
// container (`{ a: <></>, b: ... }` or
|
|
614
|
+
// `[<></>, ...]`): strip both the leaked tc_expr
|
|
615
|
+
// and b_stat so the next entry parses as an
|
|
616
|
+
// expression, and leave exprAllowed alone so a
|
|
617
|
+
// following `<` still tokenizes as jsxTagStart.
|
|
618
|
+
ctx.length = ci - 1;
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
ctx.pop();
|
|
622
|
+
this.exprAllowed = false;
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
// Tail `..., b_expr, b_expr` for fragments-with-children
|
|
627
|
+
// inside an array or object literal: re-arm expression mode
|
|
628
|
+
// so the next item parses as an expression value, not a JSX
|
|
629
|
+
// child. If the surrounding b_expr chain has already been
|
|
630
|
+
// consumed, push one back so the subsequent item still has
|
|
631
|
+
// a literal context. Leave exprAllowed alone so a following
|
|
632
|
+
// `<` still tokenizes as jsxTagStart.
|
|
633
|
+
if (top === b_expr && second === b_expr) {
|
|
634
|
+
if (ctx[ci - 2] !== b_expr && ctx[ci - 2] !== tstc.tc_oTag) {
|
|
635
|
+
ctx.push(b_expr);
|
|
636
|
+
}
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// Inside `{<tsrx>...</tsrx>}` JSX expression container — strip
|
|
642
|
+
// both the leaked `b_stat` and the container's `tc_expr`.
|
|
643
|
+
if (top === b_stat && second === tstc.tc_expr) {
|
|
644
|
+
ctx.length = ci - 1;
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
// Closing token after the template at expression position. For `}`
|
|
648
|
+
// only pop if it actually closes this `b_expr` — otherwise the
|
|
649
|
+
// brace targets an inner callback/object body that should pop it
|
|
650
|
+
// naturally on the next token step.
|
|
329
651
|
if (
|
|
330
|
-
this.
|
|
331
|
-
|
|
652
|
+
(this.type === tt.braceR &&
|
|
653
|
+
top === b_expr &&
|
|
654
|
+
(this.#countFollowingRightBraces() === 0 || second === b_expr)) ||
|
|
655
|
+
(this.type === tt.parenR && top?.token === '(') ||
|
|
656
|
+
(this.type === tt.bracketR && top?.token === '[')
|
|
332
657
|
) {
|
|
333
|
-
|
|
658
|
+
ctx.pop();
|
|
334
659
|
this.exprAllowed = false;
|
|
335
660
|
}
|
|
336
661
|
}
|
|
@@ -733,7 +1058,7 @@ export function TSRXPlugin(config) {
|
|
|
733
1058
|
|
|
734
1059
|
if (code === 60) {
|
|
735
1060
|
// < character
|
|
736
|
-
const inComponent = this.#
|
|
1061
|
+
const inComponent = this.#isInsideComponentTemplate();
|
|
737
1062
|
/** @type {number | null} */
|
|
738
1063
|
let prevNonWhitespaceChar = null;
|
|
739
1064
|
|
|
@@ -1026,6 +1351,11 @@ export function TSRXPlugin(config) {
|
|
|
1026
1351
|
skipName = false,
|
|
1027
1352
|
} = {}) {
|
|
1028
1353
|
const node = /** @type {AST.Component} */ (this.startNode());
|
|
1354
|
+
const parent_context = [...this.context];
|
|
1355
|
+
const restore_parent_context =
|
|
1356
|
+
!requireName &&
|
|
1357
|
+
this.#isInsideComponent() &&
|
|
1358
|
+
this.context.some((context) => context === tstc.tc_oTag || context === tstc.tc_cTag);
|
|
1029
1359
|
node.type = 'Component';
|
|
1030
1360
|
node.css = null;
|
|
1031
1361
|
node.default = isDefault;
|
|
@@ -1076,30 +1406,24 @@ export function TSRXPlugin(config) {
|
|
|
1076
1406
|
this.next();
|
|
1077
1407
|
}
|
|
1078
1408
|
|
|
1079
|
-
// Reset before `eat(braceL)` so the lookahead `next()` it triggers reads
|
|
1080
|
-
// the component body's first token as if we'd entered fresh — no
|
|
1081
|
-
// surrounding function body should affect our parseStatement/parseBlock
|
|
1082
|
-
// branching while inside the template.
|
|
1083
|
-
const parent_function_body_depth = this.#functionBodyDepth;
|
|
1084
|
-
this.#functionBodyDepth = 0;
|
|
1085
|
-
|
|
1086
1409
|
if (this.type === tt.braceL) {
|
|
1087
1410
|
this.#allowDoubleQuotedTextChildAfterBrace = true;
|
|
1088
1411
|
}
|
|
1089
1412
|
this.eat(tt.braceL);
|
|
1090
1413
|
node.body = [];
|
|
1091
|
-
this.#
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
}
|
|
1096
|
-
this.#functionBodyDepth = parent_function_body_depth;
|
|
1097
|
-
}
|
|
1098
|
-
this.#path.pop();
|
|
1414
|
+
this.#parseNativeTemplateBody(node, node.body, {
|
|
1415
|
+
pushPath: true,
|
|
1416
|
+
trackComponentDepth: true,
|
|
1417
|
+
resetFunctionBodyDepth: true,
|
|
1418
|
+
});
|
|
1099
1419
|
this.exitScope();
|
|
1100
1420
|
|
|
1101
1421
|
this.next();
|
|
1102
1422
|
skipWhitespace(this);
|
|
1423
|
+
if (restore_parent_context) {
|
|
1424
|
+
this.context = this.type === tt.braceR ? parent_context.slice(0, -1) : parent_context;
|
|
1425
|
+
this.exprAllowed = false;
|
|
1426
|
+
}
|
|
1103
1427
|
this.finishNode(node, 'Component');
|
|
1104
1428
|
this.awaitPos = 0;
|
|
1105
1429
|
|
|
@@ -1317,10 +1641,40 @@ export function TSRXPlugin(config) {
|
|
|
1317
1641
|
parseFunctionBody(node, isArrowFunction, isMethod, forInit, ...args) {
|
|
1318
1642
|
this.#functionBodyDepth++;
|
|
1319
1643
|
this.#functionStack.push(node);
|
|
1644
|
+
const context_restore = {
|
|
1645
|
+
parentContext: [...this.context],
|
|
1646
|
+
canRestore:
|
|
1647
|
+
this.#isInsideComponent() &&
|
|
1648
|
+
this.context.some((context) => context === tstc.tc_oTag || context === tstc.tc_cTag),
|
|
1649
|
+
restore: false,
|
|
1650
|
+
};
|
|
1651
|
+
this.#functionBodyContextRestoreStack.push(context_restore);
|
|
1652
|
+
// Inside a component, nested JS function bodies should parse like
|
|
1653
|
+
// ordinary functions, not component template bodies.
|
|
1654
|
+
if (
|
|
1655
|
+
// Only adjust functions declared while parsing a component body.
|
|
1656
|
+
this.#isInsideComponent() &&
|
|
1657
|
+
// A stale JSX expression context means the surrounding template
|
|
1658
|
+
// tokenizer can still treat `<` as template markup.
|
|
1659
|
+
this.context.some((context) => context === tstc.tc_expr) &&
|
|
1660
|
+
// Keep callback props on their surrounding JSX attribute path until
|
|
1661
|
+
// statement-position TSRX needs to suspend it.
|
|
1662
|
+
!context_restore.canRestore &&
|
|
1663
|
+
// Only reset statement-level function bodies, not expression
|
|
1664
|
+
// contexts that are actively parsing JSX.
|
|
1665
|
+
this.curContext() === b_stat
|
|
1666
|
+
) {
|
|
1667
|
+
this.context = [b_stat];
|
|
1668
|
+
}
|
|
1320
1669
|
|
|
1321
1670
|
try {
|
|
1322
1671
|
return super.parseFunctionBody(node, isArrowFunction, isMethod, forInit, ...args);
|
|
1323
1672
|
} finally {
|
|
1673
|
+
if (context_restore.restore) {
|
|
1674
|
+
this.context = context_restore.parentContext.slice(0, -1);
|
|
1675
|
+
this.exprAllowed = false;
|
|
1676
|
+
}
|
|
1677
|
+
this.#functionBodyContextRestoreStack.pop();
|
|
1324
1678
|
this.#functionStack.pop();
|
|
1325
1679
|
this.#functionBodyDepth--;
|
|
1326
1680
|
}
|
|
@@ -1929,7 +2283,9 @@ export function TSRXPlugin(config) {
|
|
|
1929
2283
|
const parsed = /** @type {import('estree-jsx').JSXElement} */ (
|
|
1930
2284
|
/** @type {unknown} */ (this.parseElement())
|
|
1931
2285
|
);
|
|
1932
|
-
this.#
|
|
2286
|
+
this.#popTokenContextsAfterTemplateExpressionElement(
|
|
2287
|
+
/** @type {AST.Tsx | AST.Tsrx | AST.TsxCompat} */ (/** @type {unknown} */ (parsed)),
|
|
2288
|
+
);
|
|
1933
2289
|
return parsed;
|
|
1934
2290
|
}
|
|
1935
2291
|
|
|
@@ -2080,9 +2436,9 @@ export function TSRXPlugin(config) {
|
|
|
2080
2436
|
this.next();
|
|
2081
2437
|
}
|
|
2082
2438
|
} else if (is_fragment) {
|
|
2083
|
-
this.
|
|
2084
|
-
|
|
2085
|
-
|
|
2439
|
+
this.#parseNativeTemplateBody(element, /** @type {AST.Element} */ (element).children, {
|
|
2440
|
+
enterScope: true,
|
|
2441
|
+
});
|
|
2086
2442
|
|
|
2087
2443
|
if (element.type === 'Tsx') {
|
|
2088
2444
|
this.#path.pop();
|
|
@@ -2249,12 +2605,7 @@ export function TSRXPlugin(config) {
|
|
|
2249
2605
|
// Ensure we escape JSX <tag></tag> context
|
|
2250
2606
|
const curContext = this.curContext();
|
|
2251
2607
|
const parent = this.#path.at(-1);
|
|
2252
|
-
const insideTemplate =
|
|
2253
|
-
parent?.type === 'Component' ||
|
|
2254
|
-
parent?.type === 'Element' ||
|
|
2255
|
-
parent?.type === 'Tsx' ||
|
|
2256
|
-
parent?.type === 'Tsrx' ||
|
|
2257
|
-
parent?.type === 'TsxCompat';
|
|
2608
|
+
const insideTemplate = this.#isNativeTemplateNode(parent);
|
|
2258
2609
|
|
|
2259
2610
|
if (curContext === tstc.tc_expr && !insideTemplate) {
|
|
2260
2611
|
this.context.pop();
|
|
@@ -2262,9 +2613,9 @@ export function TSRXPlugin(config) {
|
|
|
2262
2613
|
|
|
2263
2614
|
/** @type {AST.Element} */ (element).css = content;
|
|
2264
2615
|
} else {
|
|
2265
|
-
this.
|
|
2266
|
-
|
|
2267
|
-
|
|
2616
|
+
this.#parseNativeTemplateBody(element, /** @type {AST.Element} */ (element).children, {
|
|
2617
|
+
enterScope: true,
|
|
2618
|
+
});
|
|
2268
2619
|
|
|
2269
2620
|
if (element.type === 'Tsx') {
|
|
2270
2621
|
this.#path.pop();
|
|
@@ -2355,12 +2706,7 @@ export function TSRXPlugin(config) {
|
|
|
2355
2706
|
// Ensure we escape JSX <tag></tag> context
|
|
2356
2707
|
const curContext = this.curContext();
|
|
2357
2708
|
const parent = this.#path.at(-1);
|
|
2358
|
-
const insideTemplate =
|
|
2359
|
-
parent?.type === 'Component' ||
|
|
2360
|
-
parent?.type === 'Element' ||
|
|
2361
|
-
parent?.type === 'Tsx' ||
|
|
2362
|
-
parent?.type === 'Tsrx' ||
|
|
2363
|
-
parent?.type === 'TsxCompat';
|
|
2709
|
+
const insideTemplate = this.#isNativeTemplateNode(parent);
|
|
2364
2710
|
|
|
2365
2711
|
if (curContext === tstc.tc_expr && !insideTemplate) {
|
|
2366
2712
|
this.context.pop();
|
|
@@ -2389,8 +2735,9 @@ export function TSRXPlugin(config) {
|
|
|
2389
2735
|
parseTemplateBody(body) {
|
|
2390
2736
|
const inside_func =
|
|
2391
2737
|
this.context.some((n) => n.token === 'function') || this.scopeStack.length > 1;
|
|
2392
|
-
const
|
|
2393
|
-
|
|
2738
|
+
const inside_tsx_island = this.#path.findLast(
|
|
2739
|
+
(n) => n.type === 'Tsx' || n.type === 'TsxCompat',
|
|
2740
|
+
);
|
|
2394
2741
|
|
|
2395
2742
|
if (!inside_func) {
|
|
2396
2743
|
if (this.type.label === 'continue') {
|
|
@@ -2401,168 +2748,15 @@ export function TSRXPlugin(config) {
|
|
|
2401
2748
|
}
|
|
2402
2749
|
}
|
|
2403
2750
|
|
|
2404
|
-
if (
|
|
2405
|
-
this
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
this.start,
|
|
2411
|
-
`Unclosed tag '<tsx>'. Expected '</tsx>' before end of component.`,
|
|
2412
|
-
);
|
|
2413
|
-
inside_tsx.unclosed = true;
|
|
2414
|
-
/** @type {AST.NodeWithLocation} */ (inside_tsx).loc.end = {
|
|
2415
|
-
.../** @type {AST.SourceLocation} */ (inside_tsx.openingElement.loc).end,
|
|
2416
|
-
};
|
|
2417
|
-
inside_tsx.end = inside_tsx.openingElement.end;
|
|
2418
|
-
return;
|
|
2419
|
-
}
|
|
2420
|
-
|
|
2421
|
-
if (!inside_tsx.openingElement.name) {
|
|
2422
|
-
if (this.input.slice(this.pos, this.pos + 2) === '/>') {
|
|
2423
|
-
// Reset exprAllowed so the trailing `/` of `</>` is tokenized
|
|
2424
|
-
// as a slash rather than as the start of a regex literal.
|
|
2425
|
-
this.exprAllowed = false;
|
|
2426
|
-
return;
|
|
2427
|
-
}
|
|
2428
|
-
} else if (this.input.slice(this.pos, this.pos + 4) === '/tsx') {
|
|
2429
|
-
const after = this.input.charCodeAt(this.pos + 4);
|
|
2430
|
-
// Make sure it's </tsx> and not </tsx:...>
|
|
2431
|
-
if (after === 62 /* > */) {
|
|
2432
|
-
this.exprAllowed = false;
|
|
2433
|
-
return;
|
|
2434
|
-
}
|
|
2435
|
-
}
|
|
2436
|
-
|
|
2437
|
-
if (this.type === tt.braceL) {
|
|
2438
|
-
const node = this.jsx_parseExpressionContainer();
|
|
2439
|
-
body.push(node);
|
|
2440
|
-
} else if (this.type === tstt.jsxTagStart) {
|
|
2441
|
-
// Parse JSX element
|
|
2442
|
-
const node = super.jsx_parseElement();
|
|
2443
|
-
body.push(node);
|
|
2444
|
-
} else {
|
|
2445
|
-
const start = this.start;
|
|
2446
|
-
this.pos = start;
|
|
2447
|
-
let text = '';
|
|
2448
|
-
|
|
2449
|
-
while (this.pos < this.input.length) {
|
|
2450
|
-
const ch = this.input.charCodeAt(this.pos);
|
|
2451
|
-
|
|
2452
|
-
// Stop at opening tag, expression, or the component-closing brace
|
|
2453
|
-
if (ch === 60 || ch === 123 || ch === 125) {
|
|
2454
|
-
// < or { or }
|
|
2455
|
-
break;
|
|
2456
|
-
}
|
|
2457
|
-
|
|
2458
|
-
text += this.input[this.pos];
|
|
2459
|
-
this.pos++;
|
|
2460
|
-
}
|
|
2461
|
-
|
|
2462
|
-
if (text) {
|
|
2463
|
-
const node = /** @type {ESTreeJSX.JSXText} */ ({
|
|
2464
|
-
type: 'JSXText',
|
|
2465
|
-
value: text,
|
|
2466
|
-
raw: text,
|
|
2467
|
-
start,
|
|
2468
|
-
end: this.pos,
|
|
2469
|
-
});
|
|
2470
|
-
body.push(node);
|
|
2471
|
-
}
|
|
2472
|
-
|
|
2473
|
-
this.#popTemplateLiteralTokenContext();
|
|
2474
|
-
// Always call next() to ensure parser makes progress
|
|
2475
|
-
this.next();
|
|
2476
|
-
}
|
|
2477
|
-
}
|
|
2478
|
-
}
|
|
2479
|
-
if (inside_tsx_compat) {
|
|
2480
|
-
this.exprAllowed = true;
|
|
2481
|
-
|
|
2482
|
-
while (true) {
|
|
2483
|
-
if (this.type === tt.eof || this.pos >= this.input.length || this.type === tt.braceR) {
|
|
2484
|
-
this.#report_broken_markup_error(
|
|
2485
|
-
this.start,
|
|
2486
|
-
`Unclosed tag '<tsx:${inside_tsx_compat.kind}>'. Expected '</tsx:${inside_tsx_compat.kind}>' before end of component.`,
|
|
2487
|
-
);
|
|
2488
|
-
inside_tsx_compat.unclosed = true;
|
|
2489
|
-
/** @type {AST.NodeWithLocation} */ (inside_tsx_compat).loc.end = {
|
|
2490
|
-
.../** @type {AST.SourceLocation} */ (inside_tsx_compat.openingElement.loc).end,
|
|
2491
|
-
};
|
|
2492
|
-
inside_tsx_compat.end = inside_tsx_compat.openingElement.end;
|
|
2493
|
-
return;
|
|
2494
|
-
}
|
|
2495
|
-
|
|
2496
|
-
if (this.input.slice(this.pos, this.pos + 5) === '/tsx:') {
|
|
2497
|
-
this.exprAllowed = false;
|
|
2498
|
-
return;
|
|
2499
|
-
}
|
|
2500
|
-
|
|
2501
|
-
if (this.type === tt.braceL) {
|
|
2502
|
-
const node = this.jsx_parseExpressionContainer();
|
|
2503
|
-
body.push(node);
|
|
2504
|
-
} else if (this.type === tstt.jsxTagStart) {
|
|
2505
|
-
// Parse JSX element
|
|
2506
|
-
const node = super.jsx_parseElement();
|
|
2507
|
-
body.push(node);
|
|
2508
|
-
} else {
|
|
2509
|
-
const start = this.start;
|
|
2510
|
-
this.pos = start;
|
|
2511
|
-
let text = '';
|
|
2512
|
-
|
|
2513
|
-
while (this.pos < this.input.length) {
|
|
2514
|
-
const ch = this.input.charCodeAt(this.pos);
|
|
2515
|
-
|
|
2516
|
-
// Stop at opening tag, expression, or the component-closing brace
|
|
2517
|
-
if (ch === 60 || ch === 123 || ch === 125) {
|
|
2518
|
-
// < or { or }
|
|
2519
|
-
break;
|
|
2520
|
-
}
|
|
2521
|
-
|
|
2522
|
-
text += this.input[this.pos];
|
|
2523
|
-
this.pos++;
|
|
2524
|
-
}
|
|
2525
|
-
|
|
2526
|
-
if (text) {
|
|
2527
|
-
const node = /** @type {ESTreeJSX.JSXText} */ ({
|
|
2528
|
-
type: 'JSXText',
|
|
2529
|
-
value: text,
|
|
2530
|
-
raw: text,
|
|
2531
|
-
start,
|
|
2532
|
-
end: this.pos,
|
|
2533
|
-
});
|
|
2534
|
-
body.push(node);
|
|
2535
|
-
}
|
|
2536
|
-
|
|
2537
|
-
this.#popTemplateLiteralTokenContext();
|
|
2538
|
-
this.next();
|
|
2539
|
-
}
|
|
2540
|
-
}
|
|
2751
|
+
if (inside_tsx_island) {
|
|
2752
|
+
this.#parseTsxIslandBody(
|
|
2753
|
+
/** @type {AST.Tsx | AST.TsxCompat} */ (inside_tsx_island),
|
|
2754
|
+
/** @type {AST.Node[]} */ (/** @type {unknown} */ (body)),
|
|
2755
|
+
);
|
|
2756
|
+
return;
|
|
2541
2757
|
}
|
|
2542
2758
|
if (this.type === tt.braceL) {
|
|
2543
|
-
|
|
2544
|
-
// Keep JSXEmptyExpression as-is (for prettier to handle comments)
|
|
2545
|
-
// but convert other expressions to Html/TSRXExpression/Text nodes
|
|
2546
|
-
if (node.expression.type !== 'JSXEmptyExpression') {
|
|
2547
|
-
/** @type {AST.TSRXExpression | AST.Html | AST.TextNode | AST.Style} */ (
|
|
2548
|
-
/** @type {unknown} */ (node)
|
|
2549
|
-
).type = node.html
|
|
2550
|
-
? 'Html'
|
|
2551
|
-
: node.text
|
|
2552
|
-
? 'Text'
|
|
2553
|
-
: node.style
|
|
2554
|
-
? 'Style'
|
|
2555
|
-
: 'TSRXExpression';
|
|
2556
|
-
if (node.style) {
|
|
2557
|
-
/** @type {AST.Style} */ (/** @type {unknown} */ (node)).value =
|
|
2558
|
-
/** @type {AST.Literal} */ (node.expression);
|
|
2559
|
-
delete (/** @type {any} */ (node).expression);
|
|
2560
|
-
}
|
|
2561
|
-
delete node.html;
|
|
2562
|
-
delete node.text;
|
|
2563
|
-
delete node.style;
|
|
2564
|
-
}
|
|
2565
|
-
body.push(node);
|
|
2759
|
+
body.push(this.#parseNativeTemplateExpressionContainer());
|
|
2566
2760
|
} else if (this.type === tt.string && this.input.charCodeAt(this.start) === 34) {
|
|
2567
2761
|
body.push(this.parseDoubleQuotedTextChild());
|
|
2568
2762
|
} else if (this.type === tt.braceR) {
|
|
@@ -2811,30 +3005,8 @@ export function TSRXPlugin(config) {
|
|
|
2811
3005
|
this.type === tt.braceL &&
|
|
2812
3006
|
this.context.some((c) => c === tstc.tc_expr)
|
|
2813
3007
|
) {
|
|
2814
|
-
const node = this.jsx_parseExpressionContainer();
|
|
2815
|
-
// Keep JSXEmptyExpression as-is (don't convert to TSRXExpression/Text/Html)
|
|
2816
|
-
if (node.expression.type !== 'JSXEmptyExpression') {
|
|
2817
|
-
/** @type {AST.TSRXExpression | AST.Html | AST.TextNode | AST.Style} */ (
|
|
2818
|
-
/** @type {unknown} */ (node)
|
|
2819
|
-
).type = node.html
|
|
2820
|
-
? 'Html'
|
|
2821
|
-
: node.text
|
|
2822
|
-
? 'Text'
|
|
2823
|
-
: node.style
|
|
2824
|
-
? 'Style'
|
|
2825
|
-
: 'TSRXExpression';
|
|
2826
|
-
if (node.style) {
|
|
2827
|
-
/** @type {AST.Style} */ (/** @type {unknown} */ (node)).value =
|
|
2828
|
-
/** @type {AST.Literal} */ (node.expression);
|
|
2829
|
-
delete (/** @type {any} */ (node).expression);
|
|
2830
|
-
}
|
|
2831
|
-
delete node.html;
|
|
2832
|
-
delete node.text;
|
|
2833
|
-
delete node.style;
|
|
2834
|
-
}
|
|
2835
|
-
|
|
2836
3008
|
return /** @type {ESTreeJSX.JSXEmptyExpression | AST.TSRXExpression | AST.Html | AST.TextNode | ESTreeJSX.JSXExpressionContainer} */ (
|
|
2837
|
-
/** @type {unknown} */ (
|
|
3009
|
+
/** @type {unknown} */ (this.#parseNativeTemplateExpressionContainer())
|
|
2838
3010
|
);
|
|
2839
3011
|
}
|
|
2840
3012
|
|
|
@@ -2853,6 +3025,27 @@ export function TSRXPlugin(config) {
|
|
|
2853
3025
|
if (!node) {
|
|
2854
3026
|
this.unexpected();
|
|
2855
3027
|
}
|
|
3028
|
+
if (this.#functionBodyDepth > 0 && node.type === 'Tsrx' && this.curContext() === b_stat) {
|
|
3029
|
+
this.context.pop();
|
|
3030
|
+
if (this.curContext() === tstc.tc_expr) {
|
|
3031
|
+
this.context.pop();
|
|
3032
|
+
}
|
|
3033
|
+
if (this.curContext() === b_stat) {
|
|
3034
|
+
this.context.pop();
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
const context_restore = this.#functionBodyContextRestoreStack.at(-1);
|
|
3038
|
+
if (
|
|
3039
|
+
this.#functionBodyDepth > 0 &&
|
|
3040
|
+
node.type === 'Tsrx' &&
|
|
3041
|
+
context_restore?.canRestore &&
|
|
3042
|
+
this.type !== tt.braceR &&
|
|
3043
|
+
this.type !== tt.comma
|
|
3044
|
+
) {
|
|
3045
|
+
context_restore.restore = true;
|
|
3046
|
+
this.context = [b_stat];
|
|
3047
|
+
this.exprAllowed = true;
|
|
3048
|
+
}
|
|
2856
3049
|
return node;
|
|
2857
3050
|
}
|
|
2858
3051
|
|
|
@@ -2922,10 +3115,9 @@ export function TSRXPlugin(config) {
|
|
|
2922
3115
|
node.body = [];
|
|
2923
3116
|
this.#allowDoubleQuotedTextChildAfterBrace = true;
|
|
2924
3117
|
this.expect(tt.braceL);
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
}
|
|
2928
|
-
this.parseTemplateBody(node.body);
|
|
3118
|
+
this.#parseNativeTemplateBody(node, node.body, {
|
|
3119
|
+
enterScope: createNewLexicalScope,
|
|
3120
|
+
});
|
|
2929
3121
|
|
|
2930
3122
|
if (exitStrict) {
|
|
2931
3123
|
this.strict = false;
|
|
@@ -2933,9 +3125,6 @@ export function TSRXPlugin(config) {
|
|
|
2933
3125
|
this.exprAllowed = true;
|
|
2934
3126
|
|
|
2935
3127
|
this.next();
|
|
2936
|
-
if (createNewLexicalScope) {
|
|
2937
|
-
this.exitScope();
|
|
2938
|
-
}
|
|
2939
3128
|
return this.finishNode(node, 'BlockStatement');
|
|
2940
3129
|
}
|
|
2941
3130
|
|