@tsrx/core 0.1.16 → 0.1.17
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/README.md +4 -3
- package/package.json +1 -1
- package/src/analyze/prune.js +13 -28
- package/src/analyze/validation.js +27 -94
- package/src/diagnostics.js +1 -3
- package/src/index.js +18 -28
- package/src/parse/index.js +37 -4
- package/src/plugin.js +182 -644
- package/src/runtime/ref.js +48 -0
- package/src/scope.js +0 -13
- package/src/transform/await.js +1 -1
- package/src/transform/jsx/ast-builders.js +3 -5
- package/src/transform/jsx/helpers.js +1 -2
- package/src/transform/jsx/index.js +1257 -1491
- package/src/transform/lazy.js +6 -6
- package/src/transform/scoping.js +1 -2
- package/src/transform/segments.js +26 -157
- package/src/transform/style-ref.js +235 -0
- package/src/utils/ast.js +15 -23
- package/src/utils/builders.js +0 -18
- package/types/index.d.ts +32 -74
- package/types/jsx-platform.d.ts +20 -28
- package/types/parse.d.ts +2 -15
- package/types/runtime/ref.d.ts +3 -0
package/src/plugin.js
CHANGED
|
@@ -16,11 +16,7 @@ import {
|
|
|
16
16
|
import { regex_newline_characters } from './utils/patterns.js';
|
|
17
17
|
import { error } from './errors.js';
|
|
18
18
|
import { DIAGNOSTIC_CODES } from './diagnostics.js';
|
|
19
|
-
|
|
20
|
-
const JSX_EXPRESSION_VALUE_ERROR =
|
|
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
|
-
const HTML_ATTRIBUTE_VALUE_ERROR =
|
|
23
|
-
'`{html ...}` is not supported as an attribute value. Use a string literal or expression without `html`.';
|
|
19
|
+
import { TSRX_RETURN_STATEMENT_ERROR } from './analyze/validation.js';
|
|
24
20
|
const DYNAMIC_ELEMENT_IN_TSX_ERROR =
|
|
25
21
|
'Dynamic element syntax (`<@...>`) is only supported in native TSRX templates.';
|
|
26
22
|
const DYNAMIC_ATTRIBUTE_NAME_ERROR =
|
|
@@ -205,44 +201,9 @@ function looks_like_generic_arrow(input, pos) {
|
|
|
205
201
|
);
|
|
206
202
|
}
|
|
207
203
|
|
|
208
|
-
/**
|
|
209
|
-
* @param {AST.Node | null | undefined} node
|
|
210
|
-
* @returns {boolean}
|
|
211
|
-
*/
|
|
212
|
-
function is_pascal_case_function(node) {
|
|
213
|
-
if (node && 'id' in node && node.id && node.id.type === 'Identifier') {
|
|
214
|
-
return /^[A-Z]/.test(node.id.name);
|
|
215
|
-
}
|
|
216
|
-
return false;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* @param {string} input
|
|
221
|
-
* @param {number} pos
|
|
222
|
-
*/
|
|
223
|
-
function previous_word_before(input, pos) {
|
|
224
|
-
let i = pos - 1;
|
|
225
|
-
while (i >= 0) {
|
|
226
|
-
const ch = input.charCodeAt(i);
|
|
227
|
-
if (
|
|
228
|
-
ch !== CharCode.space &&
|
|
229
|
-
ch !== CharCode.tab &&
|
|
230
|
-
ch !== CharCode.lineFeed &&
|
|
231
|
-
ch !== CharCode.carriageReturn
|
|
232
|
-
)
|
|
233
|
-
break;
|
|
234
|
-
i--;
|
|
235
|
-
}
|
|
236
|
-
const end = i + 1;
|
|
237
|
-
while (i >= 0 && /[$_\p{ID_Continue}]/u.test(input[i])) {
|
|
238
|
-
i--;
|
|
239
|
-
}
|
|
240
|
-
return input.slice(i + 1, end);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
204
|
/**
|
|
244
205
|
* Acorn parser plugin for Ripple syntax extensions.
|
|
245
|
-
* Adds support for:
|
|
206
|
+
* Adds support for: native TSRX templates, &[]/&{} lazy destructuring,
|
|
246
207
|
* submodule imports, TSRX directives, and enhanced JSX handling.
|
|
247
208
|
*
|
|
248
209
|
* @param {import('../types/index').TSRXPluginConfig} [config] - Plugin configuration
|
|
@@ -268,18 +229,14 @@ export function TSRXPlugin(config) {
|
|
|
268
229
|
#commentContextId = 0;
|
|
269
230
|
#collect = false;
|
|
270
231
|
#loose = false;
|
|
271
|
-
/** @type {AST.Node[]} */
|
|
272
|
-
#functionStack = [];
|
|
273
|
-
/** @type {Array<{ parentContext: any[], canRestore: boolean, restore: boolean }>} */
|
|
274
|
-
#functionBodyContextRestoreStack = [];
|
|
275
232
|
/** @type {import('../types/index').CompileError[] | undefined} */
|
|
276
233
|
#errors = undefined;
|
|
277
234
|
/** @type {string | null} */
|
|
278
235
|
#filename = null;
|
|
279
|
-
#componentDepth = 0;
|
|
280
236
|
#functionBodyDepth = 0;
|
|
281
237
|
#allowExpressionContainerTrailingSemicolon = false;
|
|
282
238
|
#tsxIslandExpressionDepth = 0;
|
|
239
|
+
#jsxAttributeValueExpressionDepth = 0;
|
|
283
240
|
|
|
284
241
|
/**
|
|
285
242
|
* @type {Parse.Parser['finishNode']}
|
|
@@ -334,16 +291,8 @@ export function TSRXPlugin(config) {
|
|
|
334
291
|
return null;
|
|
335
292
|
}
|
|
336
293
|
|
|
337
|
-
#isInsideComponent() {
|
|
338
|
-
return this.#componentDepth > 0;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
#isInsideComponentTemplate() {
|
|
342
|
-
return this.#isInsideComponent() && this.#functionBodyDepth === 0;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
294
|
/**
|
|
346
|
-
*
|
|
295
|
+
* Native TSRX template bodies share one grammar across elements and fragments.
|
|
347
296
|
* This helper keeps the parser-state setup in one place while callers keep
|
|
348
297
|
* ownership of their distinct closing delimiter handling (`}` vs `</tag>`).
|
|
349
298
|
*
|
|
@@ -352,19 +301,13 @@ export function TSRXPlugin(config) {
|
|
|
352
301
|
* @param {{
|
|
353
302
|
* enterScope?: boolean,
|
|
354
303
|
* pushPath?: boolean,
|
|
355
|
-
* trackComponentDepth?: boolean,
|
|
356
304
|
* resetFunctionBodyDepth?: boolean,
|
|
357
305
|
* }} [options]
|
|
358
306
|
*/
|
|
359
307
|
#parseNativeTemplateBody(
|
|
360
308
|
node,
|
|
361
309
|
body,
|
|
362
|
-
{
|
|
363
|
-
enterScope = false,
|
|
364
|
-
pushPath = false,
|
|
365
|
-
trackComponentDepth = false,
|
|
366
|
-
resetFunctionBodyDepth = false,
|
|
367
|
-
} = {},
|
|
310
|
+
{ enterScope = false, pushPath = false, resetFunctionBodyDepth = false } = {},
|
|
368
311
|
) {
|
|
369
312
|
const parent_function_body_depth = this.#functionBodyDepth;
|
|
370
313
|
|
|
@@ -377,16 +320,10 @@ export function TSRXPlugin(config) {
|
|
|
377
320
|
if (pushPath) {
|
|
378
321
|
this.#path.push(node);
|
|
379
322
|
}
|
|
380
|
-
if (trackComponentDepth) {
|
|
381
|
-
this.#componentDepth++;
|
|
382
|
-
}
|
|
383
323
|
|
|
384
324
|
try {
|
|
385
325
|
this.parseTemplateBody(body);
|
|
386
326
|
} finally {
|
|
387
|
-
if (trackComponentDepth) {
|
|
388
|
-
this.#componentDepth--;
|
|
389
|
-
}
|
|
390
327
|
if (pushPath) {
|
|
391
328
|
this.#path.pop();
|
|
392
329
|
}
|
|
@@ -404,7 +341,6 @@ export function TSRXPlugin(config) {
|
|
|
404
341
|
*/
|
|
405
342
|
#isNativeTemplateNode(node) {
|
|
406
343
|
return (
|
|
407
|
-
node?.type === 'Component' ||
|
|
408
344
|
node?.type === 'Element' ||
|
|
409
345
|
node?.type === 'Tsx' ||
|
|
410
346
|
node?.type === 'Tsrx' ||
|
|
@@ -417,14 +353,8 @@ export function TSRXPlugin(config) {
|
|
|
417
353
|
*/
|
|
418
354
|
#reportDynamicJsxElementsInTsx(children) {
|
|
419
355
|
for (const child of children) {
|
|
420
|
-
if (child?.type === 'Tsrx') {
|
|
421
|
-
continue;
|
|
422
|
-
}
|
|
423
356
|
if (child?.type === 'JSXElement') {
|
|
424
357
|
const name = child.openingElement?.name;
|
|
425
|
-
if (name?.type === 'JSXIdentifier' && name.name === 'tsrx') {
|
|
426
|
-
continue;
|
|
427
|
-
}
|
|
428
358
|
const is_dynamic_name =
|
|
429
359
|
(name?.type === 'JSXIdentifier' && name.tracked) ||
|
|
430
360
|
(name?.type === 'JSXMemberExpression' &&
|
|
@@ -456,26 +386,11 @@ export function TSRXPlugin(config) {
|
|
|
456
386
|
// Keep JSXEmptyExpression as-is (for prettier to handle comments)
|
|
457
387
|
// but convert other expressions to native TSRX child nodes.
|
|
458
388
|
if (node.expression.type !== 'JSXEmptyExpression') {
|
|
459
|
-
/** @type {AST.TSRXExpression | AST.
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
? 'Text'
|
|
465
|
-
: node.style
|
|
466
|
-
? 'Style'
|
|
467
|
-
: 'TSRXExpression';
|
|
468
|
-
if (node.style) {
|
|
469
|
-
/** @type {AST.Style} */ (/** @type {unknown} */ (node)).value =
|
|
470
|
-
/** @type {AST.Literal} */ (node.expression);
|
|
471
|
-
delete (/** @type {any} */ (node).expression);
|
|
472
|
-
}
|
|
473
|
-
delete node.html;
|
|
474
|
-
delete node.text;
|
|
475
|
-
delete node.style;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
return /** @type {ESTreeJSX.JSXEmptyExpression | AST.TSRXExpression | AST.Html | AST.TextNode | AST.Style | ESTreeJSX.JSXExpressionContainer} */ (
|
|
389
|
+
/** @type {AST.TSRXExpression | AST.TextNode} */ (/** @type {unknown} */ (node)).type =
|
|
390
|
+
'TSRXExpression';
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return /** @type {ESTreeJSX.JSXEmptyExpression | AST.TSRXExpression | AST.TextNode | ESTreeJSX.JSXExpressionContainer} */ (
|
|
479
394
|
/** @type {unknown} */ (node)
|
|
480
395
|
);
|
|
481
396
|
}
|
|
@@ -499,7 +414,7 @@ export function TSRXPlugin(config) {
|
|
|
499
414
|
const displayTag = tagName || '';
|
|
500
415
|
this.#report_broken_markup_error(
|
|
501
416
|
this.start,
|
|
502
|
-
`Unclosed tag '<${displayTag}>'. Expected '</${displayTag}>' before end of
|
|
417
|
+
`Unclosed tag '<${displayTag}>'. Expected '</${displayTag}>' before end of template.`,
|
|
503
418
|
);
|
|
504
419
|
island.unclosed = true;
|
|
505
420
|
/** @type {AST.NodeWithLocation} */ (island).loc.end = {
|
|
@@ -585,25 +500,16 @@ export function TSRXPlugin(config) {
|
|
|
585
500
|
*/
|
|
586
501
|
#isReservedTemplateTagNameStart(index) {
|
|
587
502
|
const char_after_tsx = this.input.charCodeAt(index + 3);
|
|
588
|
-
const char_after_tsrx = this.input.charCodeAt(index + 4);
|
|
589
503
|
return (
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
(this.input.startsWith('tsrx', index) &&
|
|
600
|
-
(index + 4 >= this.input.length ||
|
|
601
|
-
char_after_tsrx === CharCode.greaterThan ||
|
|
602
|
-
char_after_tsrx === CharCode.slash ||
|
|
603
|
-
char_after_tsrx === CharCode.space ||
|
|
604
|
-
char_after_tsrx === CharCode.tab ||
|
|
605
|
-
char_after_tsrx === CharCode.lineFeed ||
|
|
606
|
-
char_after_tsrx === CharCode.carriageReturn))
|
|
504
|
+
this.input.startsWith('tsx', index) &&
|
|
505
|
+
(index + 3 >= this.input.length ||
|
|
506
|
+
char_after_tsx === CharCode.greaterThan ||
|
|
507
|
+
char_after_tsx === CharCode.slash ||
|
|
508
|
+
char_after_tsx === CharCode.space ||
|
|
509
|
+
char_after_tsx === CharCode.tab ||
|
|
510
|
+
char_after_tsx === CharCode.lineFeed ||
|
|
511
|
+
char_after_tsx === CharCode.carriageReturn ||
|
|
512
|
+
char_after_tsx === CharCode.colon)
|
|
607
513
|
);
|
|
608
514
|
}
|
|
609
515
|
|
|
@@ -635,7 +541,7 @@ export function TSRXPlugin(config) {
|
|
|
635
541
|
while (this.pos < this.input.length) {
|
|
636
542
|
const ch = this.input.charCodeAt(this.pos);
|
|
637
543
|
|
|
638
|
-
// Stop at opening tag, expression, or the
|
|
544
|
+
// Stop at opening tag, expression, or the template-closing brace
|
|
639
545
|
if (ch === CharCode.lessThan || ch === CharCode.openBrace || ch === CharCode.closeBrace) {
|
|
640
546
|
break;
|
|
641
547
|
}
|
|
@@ -827,13 +733,13 @@ export function TSRXPlugin(config) {
|
|
|
827
733
|
}
|
|
828
734
|
}
|
|
829
735
|
|
|
830
|
-
// Inside
|
|
736
|
+
// Inside a native template JSX expression container — strip
|
|
831
737
|
// both the leaked `b_stat` and the container's `tc_expr`.
|
|
832
738
|
if (top === b_stat && second === tstc.tc_expr) {
|
|
833
739
|
ctx.length = ci - 1;
|
|
834
740
|
return;
|
|
835
741
|
}
|
|
836
|
-
// Statement-bodied
|
|
742
|
+
// Statement-bodied native template attributes can leave the attribute's
|
|
837
743
|
// expression contexts above the still-open JSX tag context. Strip
|
|
838
744
|
// those so a following `/>` stays in JSX opening-tag mode.
|
|
839
745
|
if (
|
|
@@ -871,10 +777,7 @@ export function TSRXPlugin(config) {
|
|
|
871
777
|
}
|
|
872
778
|
|
|
873
779
|
const parent = this.#path.at(-1);
|
|
874
|
-
if (
|
|
875
|
-
!parent ||
|
|
876
|
-
(parent.type !== 'Component' && parent.type !== 'Element' && parent.type !== 'Tsrx')
|
|
877
|
-
) {
|
|
780
|
+
if (!parent || (parent.type !== 'Element' && parent.type !== 'Tsrx')) {
|
|
878
781
|
return false;
|
|
879
782
|
}
|
|
880
783
|
|
|
@@ -981,6 +884,75 @@ export function TSRXPlugin(config) {
|
|
|
981
884
|
this.raise(position, message);
|
|
982
885
|
}
|
|
983
886
|
|
|
887
|
+
/**
|
|
888
|
+
* @param {AST.Node | AST.Node[] | unknown} maybe_node
|
|
889
|
+
* @param {boolean} [inside_nested_function]
|
|
890
|
+
* @param {boolean} [inside_loop]
|
|
891
|
+
*/
|
|
892
|
+
#report_invalid_template_return_statements(
|
|
893
|
+
maybe_node,
|
|
894
|
+
inside_nested_function = false,
|
|
895
|
+
inside_loop = false,
|
|
896
|
+
) {
|
|
897
|
+
if (!maybe_node || typeof maybe_node !== 'object') {
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
let node = /** @type {AST.Node} */ (maybe_node);
|
|
902
|
+
if (
|
|
903
|
+
node.type === 'FunctionDeclaration' ||
|
|
904
|
+
node.type === 'FunctionExpression' ||
|
|
905
|
+
node.type === 'ArrowFunctionExpression'
|
|
906
|
+
) {
|
|
907
|
+
inside_nested_function = true;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
if (
|
|
911
|
+
node.type === 'ForStatement' ||
|
|
912
|
+
node.type === 'ForInStatement' ||
|
|
913
|
+
node.type === 'ForOfStatement' ||
|
|
914
|
+
node.type === 'WhileStatement' ||
|
|
915
|
+
node.type === 'DoWhileStatement'
|
|
916
|
+
) {
|
|
917
|
+
inside_loop = true;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if (!inside_nested_function && !inside_loop && node.type === 'ReturnStatement') {
|
|
921
|
+
node.metadata = {
|
|
922
|
+
...node.metadata,
|
|
923
|
+
invalid_tsrx_template_return: true,
|
|
924
|
+
};
|
|
925
|
+
this.#report_recoverable_error(
|
|
926
|
+
/** @type {AST.NodeWithLocation} */ (node).start ?? this.start,
|
|
927
|
+
TSRX_RETURN_STATEMENT_ERROR,
|
|
928
|
+
DIAGNOSTIC_CODES.TEMPLATE_RETURN_STATEMENT,
|
|
929
|
+
);
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
if (Array.isArray(node)) {
|
|
934
|
+
for (const child of /** @type {AST.Node[]} */ (node)) {
|
|
935
|
+
this.#report_invalid_template_return_statements(
|
|
936
|
+
child,
|
|
937
|
+
inside_nested_function,
|
|
938
|
+
inside_loop,
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
for (const key of Object.keys(node)) {
|
|
945
|
+
if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') {
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
this.#report_invalid_template_return_statements(
|
|
949
|
+
/** @type {Record<string, unknown>} */ (node)[key],
|
|
950
|
+
inside_nested_function,
|
|
951
|
+
inside_loop,
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
984
956
|
/**
|
|
985
957
|
* When collecting, keep parsing after duplicate declaration diagnostics so
|
|
986
958
|
* editor tooling can continue producing AST and mappings.
|
|
@@ -1050,64 +1022,6 @@ export function TSRXPlugin(config) {
|
|
|
1050
1022
|
}
|
|
1051
1023
|
}
|
|
1052
1024
|
|
|
1053
|
-
/**
|
|
1054
|
-
* Override parseProperty to support component methods in object literals.
|
|
1055
|
-
* Handles syntax like `{ component something() { <div /> } }`
|
|
1056
|
-
* Also supports computed names: `{ component ['something']() { <div /> } }`
|
|
1057
|
-
* @type {Parse.Parser['parseProperty']}
|
|
1058
|
-
*/
|
|
1059
|
-
parseProperty(isPattern, refDestructuringErrors) {
|
|
1060
|
-
// Check if this is a component method: component name( ... ) { ... }
|
|
1061
|
-
if (!isPattern && this.type === tt.name && this.value === 'component') {
|
|
1062
|
-
// Look ahead to see if this is "component identifier(", "component identifier<", "component [", or "component 'string'"
|
|
1063
|
-
const lookahead = this.input.slice(this.pos).match(/^\s*(?:(\w+)\s*[(<]|\[|['"])/);
|
|
1064
|
-
if (lookahead) {
|
|
1065
|
-
// This is a component method definition
|
|
1066
|
-
const prop = /** @type {AST.Property} */ (this.startNode());
|
|
1067
|
-
const isComputed = lookahead[0].trim().startsWith('[');
|
|
1068
|
-
const isStringLiteral = /^['"]/.test(lookahead[0].trim());
|
|
1069
|
-
|
|
1070
|
-
if (isComputed) {
|
|
1071
|
-
// For computed names, consume 'component'
|
|
1072
|
-
// parse the key, then parse component without name
|
|
1073
|
-
this.next(); // consume 'component'
|
|
1074
|
-
this.next(); // consume '['
|
|
1075
|
-
prop.key = this.parseExpression();
|
|
1076
|
-
this.expect(tt.bracketR);
|
|
1077
|
-
prop.computed = true;
|
|
1078
|
-
|
|
1079
|
-
// Parse component without name (skipName: true)
|
|
1080
|
-
const component_node = this.parseComponent({ skipName: true });
|
|
1081
|
-
/** @type {AST.TSRXProperty} */ (prop).value = component_node;
|
|
1082
|
-
} else if (isStringLiteral) {
|
|
1083
|
-
// For string literal names, consume 'component'
|
|
1084
|
-
// parse the string key, then parse component without name
|
|
1085
|
-
this.next(); // consume 'component'
|
|
1086
|
-
prop.key = /** @type {AST.Literal} */ (this.parseExprAtom());
|
|
1087
|
-
prop.computed = false;
|
|
1088
|
-
|
|
1089
|
-
// Parse component without name (skipName: true)
|
|
1090
|
-
const component_node = this.parseComponent({ skipName: true });
|
|
1091
|
-
/** @type {AST.TSRXProperty} */ (prop).value = component_node;
|
|
1092
|
-
} else {
|
|
1093
|
-
const component_node = this.parseComponent({ requireName: true });
|
|
1094
|
-
|
|
1095
|
-
prop.key = /** @type {AST.Identifier} */ (component_node.id);
|
|
1096
|
-
/** @type {AST.TSRXProperty} */ (prop).value = component_node;
|
|
1097
|
-
prop.computed = false;
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
prop.shorthand = false;
|
|
1101
|
-
prop.method = true;
|
|
1102
|
-
prop.kind = 'init';
|
|
1103
|
-
|
|
1104
|
-
return this.finishNode(prop, 'Property');
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
return super.parseProperty(isPattern, refDestructuringErrors);
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
1025
|
/**
|
|
1112
1026
|
* Override parsePropertyValue to support TypeScript generic methods in object literals.
|
|
1113
1027
|
* By default, acorn-typescript doesn't handle `{ method<T>() {} }` syntax.
|
|
@@ -1247,16 +1161,17 @@ export function TSRXPlugin(config) {
|
|
|
1247
1161
|
* @type {Parse.Parser['getTokenFromCode']}
|
|
1248
1162
|
*/
|
|
1249
1163
|
getTokenFromCode(code) {
|
|
1250
|
-
// Callback props that return
|
|
1164
|
+
// Callback props that return native templates without a semicolon can
|
|
1251
1165
|
// leave the attribute expression context above the still-open tag. Drop
|
|
1252
1166
|
// it before tokenizing `/>`, otherwise Acorn treats `/` as a regexp.
|
|
1253
1167
|
if (
|
|
1254
1168
|
code === CharCode.slash &&
|
|
1255
1169
|
this.input.charCodeAt(this.pos + 1) === CharCode.greaterThan &&
|
|
1256
|
-
this.
|
|
1257
|
-
this.context[this.context.length - 2] === tstc.tc_oTag
|
|
1170
|
+
this.context.includes(tstc.tc_oTag)
|
|
1258
1171
|
) {
|
|
1259
|
-
this.context.
|
|
1172
|
+
while (this.context.length > 0 && this.curContext() !== tstc.tc_oTag) {
|
|
1173
|
+
this.context.pop();
|
|
1174
|
+
}
|
|
1260
1175
|
this.exprAllowed = false;
|
|
1261
1176
|
}
|
|
1262
1177
|
if (code === CharCode.doubleQuote) {
|
|
@@ -1275,7 +1190,10 @@ export function TSRXPlugin(config) {
|
|
|
1275
1190
|
|
|
1276
1191
|
if (code === CharCode.lessThan) {
|
|
1277
1192
|
// < character
|
|
1278
|
-
const
|
|
1193
|
+
const parent = this.#path.at(-1);
|
|
1194
|
+
const inNativeTemplate =
|
|
1195
|
+
this.#functionBodyDepth === 0 &&
|
|
1196
|
+
(parent?.type === 'Element' || parent?.type === 'Tsrx');
|
|
1279
1197
|
/** @type {number | null} */
|
|
1280
1198
|
let prevNonWhitespaceChar = null;
|
|
1281
1199
|
|
|
@@ -1314,7 +1232,7 @@ export function TSRXPlugin(config) {
|
|
|
1314
1232
|
}
|
|
1315
1233
|
}
|
|
1316
1234
|
|
|
1317
|
-
// Support parsing standalone template markup at the top-level
|
|
1235
|
+
// Support parsing standalone template markup at the top-level
|
|
1318
1236
|
// for tooling like Prettier, e.g.:
|
|
1319
1237
|
// <Something>...</Something>\n\n<Child />
|
|
1320
1238
|
// <head><style>...</style></head>
|
|
@@ -1343,13 +1261,13 @@ export function TSRXPlugin(config) {
|
|
|
1343
1261
|
prevNonWhitespaceChar === CharCode.closeBrace ||
|
|
1344
1262
|
prevNonWhitespaceChar === CharCode.greaterThan;
|
|
1345
1263
|
|
|
1346
|
-
if (!
|
|
1264
|
+
if (!inNativeTemplate && prevAllowsTagStart && isTagLikeAfterLt) {
|
|
1347
1265
|
++this.pos;
|
|
1348
1266
|
return this.finishToken(tstt.jsxTagStart);
|
|
1349
1267
|
}
|
|
1350
1268
|
|
|
1351
|
-
if (
|
|
1352
|
-
// Inside
|
|
1269
|
+
if (inNativeTemplate) {
|
|
1270
|
+
// Inside native template bodies, allow adjacent tags without requiring
|
|
1353
1271
|
// a newline/indentation before the next '<'. This is important for inputs
|
|
1354
1272
|
// like `<div />` and `</div><style>...</style>` which Prettier formats.
|
|
1355
1273
|
if (
|
|
@@ -1488,36 +1406,6 @@ export function TSRXPlugin(config) {
|
|
|
1488
1406
|
return super.checkLValSimple(expr, bindingType, checkClashes);
|
|
1489
1407
|
}
|
|
1490
1408
|
|
|
1491
|
-
/**
|
|
1492
|
-
* Components do not use Acorn's normal function-body parser, but they
|
|
1493
|
-
* should still report duplicate parameter names like functions do. Keep
|
|
1494
|
-
* this validation on `BIND_OUTSIDE` so params are checked without being
|
|
1495
|
-
* declared in the component template scope, preserving existing shadowing
|
|
1496
|
-
* behavior.
|
|
1497
|
-
*
|
|
1498
|
-
* @param {AST.Pattern[]} params
|
|
1499
|
-
*/
|
|
1500
|
-
checkComponentParams(params) {
|
|
1501
|
-
/** @type {Record<string, boolean>} */
|
|
1502
|
-
const name_hash = Object.create(null);
|
|
1503
|
-
for (const param of params || []) {
|
|
1504
|
-
this.checkLValInnerPattern(param, BINDING_TYPES.BIND_OUTSIDE, name_hash);
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
|
|
1508
|
-
/**
|
|
1509
|
-
* Parse expression atom - handles RippleArray and RippleObject literals
|
|
1510
|
-
* @type {Parse.Parser['parseExprAtom']}
|
|
1511
|
-
*/
|
|
1512
|
-
parseExprAtom(refDestructuringErrors, forNew, forInit) {
|
|
1513
|
-
// Check if this is a component expression (e.g., in object literal values)
|
|
1514
|
-
if (this.type === tt.name && this.value === 'component') {
|
|
1515
|
-
return this.parseComponent();
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
return super.parseExprAtom(refDestructuringErrors, forNew, forInit);
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
1409
|
/**
|
|
1522
1410
|
* Override to track parenthesized expressions in metadata
|
|
1523
1411
|
* This allows the prettier plugin to preserve parentheses where they existed
|
|
@@ -1560,108 +1448,6 @@ export function TSRXPlugin(config) {
|
|
|
1560
1448
|
this.undefinedExports[name] = id;
|
|
1561
1449
|
}
|
|
1562
1450
|
|
|
1563
|
-
/**
|
|
1564
|
-
* Parse a component - common implementation used by statements, expressions, and export defaults
|
|
1565
|
-
* @type {Parse.Parser['parseComponent']}
|
|
1566
|
-
*/
|
|
1567
|
-
parseComponent({
|
|
1568
|
-
requireName = false,
|
|
1569
|
-
isDefault = false,
|
|
1570
|
-
declareName = false,
|
|
1571
|
-
skipName = false,
|
|
1572
|
-
} = {}) {
|
|
1573
|
-
const node = /** @type {AST.Component} */ (this.startNode());
|
|
1574
|
-
const parent_context = [...this.context];
|
|
1575
|
-
const restore_parent_context =
|
|
1576
|
-
!requireName &&
|
|
1577
|
-
this.#isInsideComponent() &&
|
|
1578
|
-
this.context.some((context) => context === tstc.tc_oTag || context === tstc.tc_cTag);
|
|
1579
|
-
node.type = 'Component';
|
|
1580
|
-
node.css = null;
|
|
1581
|
-
node.default = isDefault;
|
|
1582
|
-
|
|
1583
|
-
// skipName is used for computed property names where 'component' and the key
|
|
1584
|
-
// have already been consumed before calling parseComponent
|
|
1585
|
-
if (!skipName) {
|
|
1586
|
-
this.next(); // consume 'component'
|
|
1587
|
-
}
|
|
1588
|
-
this.enterScope(0);
|
|
1589
|
-
|
|
1590
|
-
if (skipName) {
|
|
1591
|
-
// For computed names, the key is parsed separately, so id is null
|
|
1592
|
-
node.id = null;
|
|
1593
|
-
} else if (requireName) {
|
|
1594
|
-
node.id = this.parseIdent();
|
|
1595
|
-
if (declareName) {
|
|
1596
|
-
this.declareName(
|
|
1597
|
-
node.id.name,
|
|
1598
|
-
BINDING_TYPES.BIND_FUNCTION,
|
|
1599
|
-
/** @type {AST.NodeWithLocation} */ (node.id).start,
|
|
1600
|
-
);
|
|
1601
|
-
}
|
|
1602
|
-
} else {
|
|
1603
|
-
node.id = this.type.label === 'name' ? this.parseIdent() : null;
|
|
1604
|
-
if (declareName && node.id) {
|
|
1605
|
-
this.declareName(
|
|
1606
|
-
node.id.name,
|
|
1607
|
-
BINDING_TYPES.BIND_FUNCTION,
|
|
1608
|
-
/** @type {AST.NodeWithLocation} */ (node.id).start,
|
|
1609
|
-
);
|
|
1610
|
-
}
|
|
1611
|
-
}
|
|
1612
|
-
|
|
1613
|
-
this.parseFunctionParams(node);
|
|
1614
|
-
this.checkComponentParams(node.params);
|
|
1615
|
-
|
|
1616
|
-
const is_arrow_component = this.type === tt.arrow;
|
|
1617
|
-
if (is_arrow_component) {
|
|
1618
|
-
if (node.id || requireName || skipName) {
|
|
1619
|
-
this.raise(
|
|
1620
|
-
this.start,
|
|
1621
|
-
'Arrow component syntax is only supported for anonymous component expressions.',
|
|
1622
|
-
);
|
|
1623
|
-
}
|
|
1624
|
-
node.metadata ??= { path: [] };
|
|
1625
|
-
node.metadata.arrow = true;
|
|
1626
|
-
this.next();
|
|
1627
|
-
}
|
|
1628
|
-
|
|
1629
|
-
if (this.type === tt.braceL) {
|
|
1630
|
-
this.#allowDoubleQuotedTextChildAfterBrace = true;
|
|
1631
|
-
}
|
|
1632
|
-
this.eat(tt.braceL);
|
|
1633
|
-
node.body = [];
|
|
1634
|
-
this.#parseNativeTemplateBody(node, node.body, {
|
|
1635
|
-
pushPath: true,
|
|
1636
|
-
trackComponentDepth: true,
|
|
1637
|
-
resetFunctionBodyDepth: true,
|
|
1638
|
-
});
|
|
1639
|
-
this.exitScope();
|
|
1640
|
-
|
|
1641
|
-
this.next();
|
|
1642
|
-
skipWhitespace(this);
|
|
1643
|
-
if (restore_parent_context) {
|
|
1644
|
-
this.context = this.type === tt.braceR ? parent_context.slice(0, -1) : parent_context;
|
|
1645
|
-
this.exprAllowed = false;
|
|
1646
|
-
}
|
|
1647
|
-
this.finishNode(node, 'Component');
|
|
1648
|
-
this.awaitPos = 0;
|
|
1649
|
-
|
|
1650
|
-
return node;
|
|
1651
|
-
}
|
|
1652
|
-
|
|
1653
|
-
/**
|
|
1654
|
-
* @type {Parse.Parser['parseExportDefaultDeclaration']}
|
|
1655
|
-
*/
|
|
1656
|
-
parseExportDefaultDeclaration() {
|
|
1657
|
-
// Check if this is "export default component"
|
|
1658
|
-
if (this.value === 'component') {
|
|
1659
|
-
return this.parseComponent({ isDefault: true });
|
|
1660
|
-
}
|
|
1661
|
-
|
|
1662
|
-
return super.parseExportDefaultDeclaration();
|
|
1663
|
-
}
|
|
1664
|
-
|
|
1665
1451
|
/** @type {Parse.Parser['parseForStatement']} */
|
|
1666
1452
|
parseForStatement(node) {
|
|
1667
1453
|
this.next();
|
|
@@ -1860,77 +1646,13 @@ export function TSRXPlugin(config) {
|
|
|
1860
1646
|
*/
|
|
1861
1647
|
parseFunctionBody(node, isArrowFunction, isMethod, forInit, ...args) {
|
|
1862
1648
|
this.#functionBodyDepth++;
|
|
1863
|
-
this.#functionStack.push(node);
|
|
1864
|
-
const context_restore = {
|
|
1865
|
-
parentContext: [...this.context],
|
|
1866
|
-
canRestore:
|
|
1867
|
-
this.#isInsideComponent() &&
|
|
1868
|
-
this.context.some((context) => context === tstc.tc_oTag || context === tstc.tc_cTag),
|
|
1869
|
-
restore: false,
|
|
1870
|
-
};
|
|
1871
|
-
this.#functionBodyContextRestoreStack.push(context_restore);
|
|
1872
|
-
// Inside a component, nested JS function bodies should parse like
|
|
1873
|
-
// ordinary functions, not component template bodies.
|
|
1874
|
-
if (
|
|
1875
|
-
// Only adjust functions declared while parsing a component body.
|
|
1876
|
-
this.#isInsideComponent() &&
|
|
1877
|
-
// A stale JSX expression context means the surrounding template
|
|
1878
|
-
// tokenizer can still treat `<` as template markup.
|
|
1879
|
-
this.context.some((context) => context === tstc.tc_expr) &&
|
|
1880
|
-
// Keep callback props on their surrounding JSX attribute path until
|
|
1881
|
-
// statement-position TSRX needs to suspend it.
|
|
1882
|
-
!context_restore.canRestore &&
|
|
1883
|
-
// Only reset statement-level function bodies, not expression
|
|
1884
|
-
// contexts that are actively parsing JSX.
|
|
1885
|
-
this.curContext() === b_stat
|
|
1886
|
-
) {
|
|
1887
|
-
this.context = [b_stat];
|
|
1888
|
-
}
|
|
1889
|
-
|
|
1890
1649
|
try {
|
|
1891
1650
|
return super.parseFunctionBody(node, isArrowFunction, isMethod, forInit, ...args);
|
|
1892
1651
|
} finally {
|
|
1893
|
-
if (context_restore.restore) {
|
|
1894
|
-
this.context = context_restore.parentContext.slice(0, -1);
|
|
1895
|
-
this.exprAllowed = false;
|
|
1896
|
-
}
|
|
1897
|
-
this.#functionBodyContextRestoreStack.pop();
|
|
1898
|
-
this.#functionStack.pop();
|
|
1899
1652
|
this.#functionBodyDepth--;
|
|
1900
1653
|
}
|
|
1901
1654
|
}
|
|
1902
1655
|
|
|
1903
|
-
/**
|
|
1904
|
-
* @type {Parse.Parser['checkUnreserved']}
|
|
1905
|
-
*/
|
|
1906
|
-
checkUnreserved(ref) {
|
|
1907
|
-
if (ref.name === 'component') {
|
|
1908
|
-
// Allow 'component' when it's followed by an identifier and '(' or '<' (component method in object literal)
|
|
1909
|
-
// e.g., { component something() { ... } }
|
|
1910
|
-
// Also allow computed names: { component ['name']() { ... } }
|
|
1911
|
-
// Also allow string literal names: { component 'name'() { ... } }
|
|
1912
|
-
const nextChars = this.input.slice(this.pos).match(/^\s*(?:(\w+)\s*[(<]|\[|['"])/);
|
|
1913
|
-
if (!nextChars) {
|
|
1914
|
-
this.raise(
|
|
1915
|
-
ref.start,
|
|
1916
|
-
'"component" is a TSRX keyword and cannot be used as an identifier',
|
|
1917
|
-
);
|
|
1918
|
-
}
|
|
1919
|
-
}
|
|
1920
|
-
return super.checkUnreserved(ref);
|
|
1921
|
-
}
|
|
1922
|
-
|
|
1923
|
-
/** @type {Parse.Parser['shouldParseExportStatement']} */
|
|
1924
|
-
shouldParseExportStatement() {
|
|
1925
|
-
if (super.shouldParseExportStatement()) {
|
|
1926
|
-
return true;
|
|
1927
|
-
}
|
|
1928
|
-
if (this.value === 'component') {
|
|
1929
|
-
return true;
|
|
1930
|
-
}
|
|
1931
|
-
return this.type.keyword === 'var';
|
|
1932
|
-
}
|
|
1933
|
-
|
|
1934
1656
|
/**
|
|
1935
1657
|
* @return {ESTreeJSX.JSXExpressionContainer}
|
|
1936
1658
|
*/
|
|
@@ -1938,59 +1660,8 @@ export function TSRXPlugin(config) {
|
|
|
1938
1660
|
let node = /** @type {ESTreeJSX.JSXExpressionContainer} */ (this.startNode());
|
|
1939
1661
|
this.next();
|
|
1940
1662
|
|
|
1941
|
-
if (this.type === tt.name && this.value === 'ref') {
|
|
1942
|
-
const ref_node = /** @type {AST.RefExpression} */ (this.startNode());
|
|
1943
|
-
this.next();
|
|
1944
|
-
if (this.type === tt.braceR) {
|
|
1945
|
-
this.raise(
|
|
1946
|
-
this.start,
|
|
1947
|
-
'"ref" is a TSRX keyword and must be used in the form {ref item}',
|
|
1948
|
-
);
|
|
1949
|
-
}
|
|
1950
|
-
ref_node.argument = this.parseMaybeAssign();
|
|
1951
|
-
node.expression = /** @type {any} */ (this.finishNode(ref_node, 'RefExpression'));
|
|
1952
|
-
this.expect(tt.braceR);
|
|
1953
|
-
return this.finishNode(node, 'JSXExpressionContainer');
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
|
-
if (this.type === tt.name && this.value === 'html') {
|
|
1957
|
-
node.html = true;
|
|
1958
|
-
this.next();
|
|
1959
|
-
if (this.type === tt.braceR) {
|
|
1960
|
-
this.raise(
|
|
1961
|
-
this.start,
|
|
1962
|
-
'"html" is a TSRX keyword and must be used in the form {html some_content}',
|
|
1963
|
-
);
|
|
1964
|
-
}
|
|
1965
|
-
} else if (this.type === tt.name && this.value === 'text') {
|
|
1966
|
-
node.text = true;
|
|
1967
|
-
this.next();
|
|
1968
|
-
if (this.type === tt.braceR) {
|
|
1969
|
-
this.raise(
|
|
1970
|
-
this.start,
|
|
1971
|
-
'"text" is a TSRX keyword and must be used in the form {text some_value}',
|
|
1972
|
-
);
|
|
1973
|
-
}
|
|
1974
|
-
} else if (
|
|
1975
|
-
this.type === tt.name &&
|
|
1976
|
-
this.value === 'style' &&
|
|
1977
|
-
this.lookahead().type === tt.string
|
|
1978
|
-
) {
|
|
1979
|
-
node.style = true;
|
|
1980
|
-
this.next();
|
|
1981
|
-
}
|
|
1982
|
-
|
|
1983
1663
|
node.expression =
|
|
1984
1664
|
this.type === tt.braceR ? this.jsx_parseEmptyExpression() : this.parseExpression();
|
|
1985
|
-
if (
|
|
1986
|
-
node.style &&
|
|
1987
|
-
(node.expression.type !== 'Literal' || typeof node.expression.value !== 'string')
|
|
1988
|
-
) {
|
|
1989
|
-
this.raise(
|
|
1990
|
-
/** @type {number} */ (node.expression.start),
|
|
1991
|
-
'"style" is a TSRX keyword and must be used in the form {style "class_name"}',
|
|
1992
|
-
);
|
|
1993
|
-
}
|
|
1994
1665
|
if (this.#allowExpressionContainerTrailingSemicolon && this.type === tt.semi) {
|
|
1995
1666
|
if (this.#collect) {
|
|
1996
1667
|
this.#report_recoverable_error(
|
|
@@ -2088,39 +1759,7 @@ export function TSRXPlugin(config) {
|
|
|
2088
1759
|
this.unexpected();
|
|
2089
1760
|
}
|
|
2090
1761
|
|
|
2091
|
-
if (this.
|
|
2092
|
-
this.next();
|
|
2093
|
-
if (this.type === tt.braceR) {
|
|
2094
|
-
this.raise(
|
|
2095
|
-
this.start,
|
|
2096
|
-
'"ref" is a Ripple keyword and must be used in the form {ref fn}',
|
|
2097
|
-
);
|
|
2098
|
-
}
|
|
2099
|
-
/** @type {AST.RefAttribute} */ (node).argument = this.parseMaybeAssign();
|
|
2100
|
-
this.expect(tt.braceR);
|
|
2101
|
-
return /** @type {AST.RefAttribute} */ (this.finishNode(node, 'RefAttribute'));
|
|
2102
|
-
} else if (this.type === tt.name && this.value === 'html') {
|
|
2103
|
-
// {html ...}
|
|
2104
|
-
// The support is purely for better error messages to avoid
|
|
2105
|
-
// the parser throw an unexpected token error
|
|
2106
|
-
const id = /** @type {AST.Identifier} */ (this.parseIdentNode());
|
|
2107
|
-
id.tracked = false;
|
|
2108
|
-
this.finishNode(id, 'Identifier');
|
|
2109
|
-
this.next();
|
|
2110
|
-
const value = this.type === tt.braceR ? id : this.parseMaybeAssign();
|
|
2111
|
-
const report_end = this.type === tt.braceR ? this.end : (value.end ?? this.end);
|
|
2112
|
-
this.#report_recoverable_error_range(
|
|
2113
|
-
node.start ?? id.start ?? this.start,
|
|
2114
|
-
report_end,
|
|
2115
|
-
HTML_ATTRIBUTE_VALUE_ERROR,
|
|
2116
|
-
DIAGNOSTIC_CODES.HTML_DIRECTIVE_AS_ATTRIBUTE_VALUE,
|
|
2117
|
-
);
|
|
2118
|
-
/** @type {AST.Attribute} */ (node).name = id;
|
|
2119
|
-
/** @type {AST.Attribute} */ (node).value = value;
|
|
2120
|
-
/** @type {AST.Attribute} */ (node).shorthand = false;
|
|
2121
|
-
this.expect(tt.braceR);
|
|
2122
|
-
return this.finishNode(node, 'Attribute');
|
|
2123
|
-
} else if (this.type === tt.ellipsis) {
|
|
1762
|
+
if (this.type === tt.ellipsis) {
|
|
2124
1763
|
this.expect(tt.ellipsis);
|
|
2125
1764
|
/** @type {AST.SpreadAttribute} */ (node).argument = this.parseMaybeAssign();
|
|
2126
1765
|
this.expect(tt.braceR);
|
|
@@ -2160,14 +1799,6 @@ export function TSRXPlugin(config) {
|
|
|
2160
1799
|
const value = /** @type {ESTreeJSX.JSXAttribute['value'] | null} */ (
|
|
2161
1800
|
this.eat(tt.eq) ? this.jsx_parseAttributeValue() : null
|
|
2162
1801
|
);
|
|
2163
|
-
if (value?.type === 'JSXExpressionContainer' && value.html) {
|
|
2164
|
-
this.#report_recoverable_error_range(
|
|
2165
|
-
value.start ?? node.start ?? this.start,
|
|
2166
|
-
value.end ?? node.end ?? this.end,
|
|
2167
|
-
HTML_ATTRIBUTE_VALUE_ERROR,
|
|
2168
|
-
DIAGNOSTIC_CODES.HTML_DIRECTIVE_AS_ATTRIBUTE_VALUE,
|
|
2169
|
-
);
|
|
2170
|
-
}
|
|
2171
1802
|
/** @type {ESTreeJSX.JSXAttribute} */ (node).value = value;
|
|
2172
1803
|
return this.finishNode(node, 'JSXAttribute');
|
|
2173
1804
|
}
|
|
@@ -2263,7 +1894,12 @@ export function TSRXPlugin(config) {
|
|
|
2263
1894
|
jsx_parseAttributeValue() {
|
|
2264
1895
|
switch (this.type) {
|
|
2265
1896
|
case tt.braceL:
|
|
2266
|
-
|
|
1897
|
+
this.#jsxAttributeValueExpressionDepth++;
|
|
1898
|
+
try {
|
|
1899
|
+
return this.jsx_parseExpressionContainer();
|
|
1900
|
+
} finally {
|
|
1901
|
+
this.#jsxAttributeValueExpressionDepth--;
|
|
1902
|
+
}
|
|
2267
1903
|
case tstt.jsxTagStart:
|
|
2268
1904
|
case tt.string:
|
|
2269
1905
|
return this.parseExprAtom();
|
|
@@ -2480,7 +2116,6 @@ export function TSRXPlugin(config) {
|
|
|
2480
2116
|
if (
|
|
2481
2117
|
ch === CharCode.closeBrace &&
|
|
2482
2118
|
(this.#path.length === 0 ||
|
|
2483
|
-
this.#path.at(-1)?.type === 'Component' ||
|
|
2484
2119
|
this.#path.at(-1)?.type === 'Element' ||
|
|
2485
2120
|
this.#path.at(-1)?.type === 'Tsrx')
|
|
2486
2121
|
) {
|
|
@@ -2519,49 +2154,21 @@ export function TSRXPlugin(config) {
|
|
|
2519
2154
|
}
|
|
2520
2155
|
|
|
2521
2156
|
/**
|
|
2522
|
-
* Override jsx_parseElement to
|
|
2523
|
-
*
|
|
2524
|
-
*
|
|
2525
|
-
* for <tsx>...</tsx>. <tsrx>...</tsrx> admits native TSRX
|
|
2526
|
-
* template syntax as an expression value. Other tags must still use
|
|
2527
|
-
* <tsx>, <tsrx>, or <tsx:*>.
|
|
2157
|
+
* Override jsx_parseElement to parse tags and bare fragments as native TSRX
|
|
2158
|
+
* by default. Explicit <tsx> and <tsx:*> islands keep ordinary TSX parsing
|
|
2159
|
+
* for their children.
|
|
2528
2160
|
* @type {Parse.Parser['jsx_parseElement']}
|
|
2529
2161
|
*/
|
|
2530
2162
|
jsx_parseElement() {
|
|
2531
|
-
// Check if the element being parsed IS a <tsx>, <tsrx>, or <tsx:*> tag
|
|
2532
2163
|
// Current token is jsxTagStart, this.end is position after '<'
|
|
2533
2164
|
const tag_name_start = this.end;
|
|
2534
|
-
const is_fragment_tag = this.input.charCodeAt(tag_name_start) === CharCode.greaterThan;
|
|
2535
|
-
const char_after_tsx = this.input.charCodeAt(tag_name_start + 3);
|
|
2536
|
-
const char_after_tsrx = this.input.charCodeAt(tag_name_start + 4);
|
|
2537
|
-
const is_tsx_tag =
|
|
2538
|
-
this.input.startsWith('tsx', tag_name_start) &&
|
|
2539
|
-
(tag_name_start + 3 >= this.input.length ||
|
|
2540
|
-
char_after_tsx === CharCode.greaterThan ||
|
|
2541
|
-
char_after_tsx === CharCode.slash ||
|
|
2542
|
-
char_after_tsx === CharCode.space ||
|
|
2543
|
-
char_after_tsx === CharCode.tab ||
|
|
2544
|
-
char_after_tsx === CharCode.lineFeed ||
|
|
2545
|
-
char_after_tsx === CharCode.carriageReturn ||
|
|
2546
|
-
char_after_tsx === CharCode.colon);
|
|
2547
|
-
const is_tsrx_tag =
|
|
2548
|
-
this.input.startsWith('tsrx', tag_name_start) &&
|
|
2549
|
-
(tag_name_start + 4 >= this.input.length ||
|
|
2550
|
-
char_after_tsrx === CharCode.greaterThan ||
|
|
2551
|
-
char_after_tsrx === CharCode.slash ||
|
|
2552
|
-
char_after_tsrx === CharCode.space ||
|
|
2553
|
-
char_after_tsrx === CharCode.tab ||
|
|
2554
|
-
char_after_tsrx === CharCode.lineFeed ||
|
|
2555
|
-
char_after_tsrx === CharCode.carriageReturn);
|
|
2556
|
-
|
|
2557
2165
|
const current_template_node = this.#path.findLast(
|
|
2558
2166
|
(n) =>
|
|
2559
2167
|
n.type === 'Element' || n.type === 'Tsx' || n.type === 'Tsrx' || n.type === 'TsxCompat',
|
|
2560
2168
|
);
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
) {
|
|
2169
|
+
const inside_tsx_island =
|
|
2170
|
+
current_template_node?.type === 'Tsx' || current_template_node?.type === 'TsxCompat';
|
|
2171
|
+
if (inside_tsx_island) {
|
|
2565
2172
|
if (this.input.charCodeAt(tag_name_start) === CharCode.at) {
|
|
2566
2173
|
this.#report_recoverable_error_range(
|
|
2567
2174
|
this.start,
|
|
@@ -2570,53 +2177,26 @@ export function TSRXPlugin(config) {
|
|
|
2570
2177
|
);
|
|
2571
2178
|
}
|
|
2572
2179
|
// Inside tsx/tsx:*, let acorn-jsx handle regular TSX tags normally.
|
|
2573
|
-
// Nested <tsrx> still needs Ripple's native template parser so it
|
|
2574
|
-
// can lower through the same path as <tsrx> in component bodies.
|
|
2575
2180
|
return super.jsx_parseElement();
|
|
2576
2181
|
}
|
|
2577
2182
|
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2183
|
+
this.next();
|
|
2184
|
+
const parsed = /** @type {import('estree-jsx').JSXElement} */ (
|
|
2185
|
+
/** @type {unknown} */ (this.parseElement())
|
|
2186
|
+
);
|
|
2187
|
+
if (!inside_tsx_island) {
|
|
2188
|
+
this.#popTokenContextsAfterTemplateExpressionElement(
|
|
2189
|
+
/** @type {AST.Tsx | AST.Tsrx | AST.TsxCompat} */ (/** @type {unknown} */ (parsed)),
|
|
2584
2190
|
);
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
);
|
|
2592
|
-
} else if (this.type === tt.braceR && this.curContext() === tstc.tc_expr) {
|
|
2593
|
-
if (this.#tsxIslandExpressionDepth === 0) {
|
|
2594
|
-
// Acorn still owns the surrounding JSX expression container.
|
|
2595
|
-
// Keep a block-expression context for its closing `}` so the
|
|
2596
|
-
// parent TSX tag continues tokenizing as JSX afterward.
|
|
2597
|
-
this.context.push(b_expr);
|
|
2598
|
-
}
|
|
2191
|
+
} else if (this.type === tt.braceR && this.curContext() === tstc.tc_expr) {
|
|
2192
|
+
if (this.#tsxIslandExpressionDepth === 0) {
|
|
2193
|
+
// Acorn still owns the surrounding JSX expression container.
|
|
2194
|
+
// Keep a block-expression context for its closing `}` so the
|
|
2195
|
+
// parent TSX tag continues tokenizing as JSX afterward.
|
|
2196
|
+
this.context.push(b_expr);
|
|
2599
2197
|
}
|
|
2600
|
-
return parsed;
|
|
2601
2198
|
}
|
|
2602
|
-
|
|
2603
|
-
if (
|
|
2604
|
-
!this.#path.findLast((node) => node.type === 'Component') &&
|
|
2605
|
-
!this.#functionStack.findLast(is_pascal_case_function)
|
|
2606
|
-
) {
|
|
2607
|
-
return super.jsx_parseElement();
|
|
2608
|
-
}
|
|
2609
|
-
|
|
2610
|
-
const code = this.#functionStack.findLast(is_pascal_case_function)
|
|
2611
|
-
? DIAGNOSTIC_CODES.FUNCTION_COMPONENT_SYNTAX
|
|
2612
|
-
: this.#path.findLast((node) => node.type === 'Component') &&
|
|
2613
|
-
this.#functionStack.length === 0 &&
|
|
2614
|
-
previous_word_before(this.input, this.start) === 'return'
|
|
2615
|
-
? DIAGNOSTIC_CODES.JSX_RETURN_IN_COMPONENT
|
|
2616
|
-
: DIAGNOSTIC_CODES.JSX_EXPRESSION_VALUE;
|
|
2617
|
-
|
|
2618
|
-
this.#report_recoverable_error(this.start, JSX_EXPRESSION_VALUE_ERROR, code);
|
|
2619
|
-
return super.jsx_parseElement();
|
|
2199
|
+
return parsed;
|
|
2620
2200
|
}
|
|
2621
2201
|
|
|
2622
2202
|
/**
|
|
@@ -2655,11 +2235,6 @@ export function TSRXPlugin(config) {
|
|
|
2655
2235
|
!is_tsx_compat &&
|
|
2656
2236
|
open.name.type === 'JSXIdentifier' &&
|
|
2657
2237
|
open.name.name === 'tsx';
|
|
2658
|
-
const is_tsrx =
|
|
2659
|
-
!is_fragment &&
|
|
2660
|
-
!is_tsx_compat &&
|
|
2661
|
-
open.name.type === 'JSXIdentifier' &&
|
|
2662
|
-
open.name.name === 'tsrx';
|
|
2663
2238
|
const is_dynamic_name =
|
|
2664
2239
|
!is_fragment &&
|
|
2665
2240
|
((open.name.type === 'JSXIdentifier' && open.name.tracked) ||
|
|
@@ -2688,22 +2263,13 @@ export function TSRXPlugin(config) {
|
|
|
2688
2263
|
`TSX elements cannot be self-closing. '<tsx />' must have a closing tag '</tsx>'.`,
|
|
2689
2264
|
);
|
|
2690
2265
|
}
|
|
2691
|
-
} else if (is_tsrx) {
|
|
2692
|
-
/** @type {AST.Tsrx} */ (element).type = 'Tsrx';
|
|
2693
|
-
|
|
2694
|
-
if (open.selfClosing) {
|
|
2695
|
-
this.raise(
|
|
2696
|
-
open.start,
|
|
2697
|
-
`TSRX elements cannot be self-closing. '<tsrx />' must have a closing tag '</tsrx>'.`,
|
|
2698
|
-
);
|
|
2699
|
-
}
|
|
2700
2266
|
} else if (is_fragment) {
|
|
2701
|
-
/** @type {AST.
|
|
2267
|
+
/** @type {AST.Tsrx} */ (element).type = 'Tsrx';
|
|
2702
2268
|
} else {
|
|
2703
2269
|
element.type = 'Element';
|
|
2704
2270
|
}
|
|
2705
2271
|
|
|
2706
|
-
if (
|
|
2272
|
+
if (is_tsx && is_dynamic_name) {
|
|
2707
2273
|
this.#report_recoverable_error_range(
|
|
2708
2274
|
open.name.start ?? open.start,
|
|
2709
2275
|
open.name.end ?? open.end,
|
|
@@ -2721,14 +2287,6 @@ export function TSRXPlugin(config) {
|
|
|
2721
2287
|
if (attr.value !== null) {
|
|
2722
2288
|
if (attr.value.type === 'JSXExpressionContainer') {
|
|
2723
2289
|
const expression = attr.value.expression;
|
|
2724
|
-
if (attr.value.style) {
|
|
2725
|
-
/** @type {AST.Style} */ (/** @type {unknown} */ (attr.value)).type = 'Style';
|
|
2726
|
-
/** @type {AST.Style} */ (/** @type {unknown} */ (attr.value)).value =
|
|
2727
|
-
/** @type {AST.Literal} */ (expression);
|
|
2728
|
-
delete (/** @type {any} */ (attr.value).expression);
|
|
2729
|
-
delete (/** @type {any} */ (attr.value).style);
|
|
2730
|
-
continue;
|
|
2731
|
-
}
|
|
2732
2290
|
if (expression.type === 'Literal') {
|
|
2733
2291
|
expression.was_expression = true;
|
|
2734
2292
|
}
|
|
@@ -2740,7 +2298,7 @@ export function TSRXPlugin(config) {
|
|
|
2740
2298
|
}
|
|
2741
2299
|
}
|
|
2742
2300
|
|
|
2743
|
-
if (!is_tsx_compat && !is_tsx && !
|
|
2301
|
+
if (!is_tsx_compat && !is_tsx && !is_fragment) {
|
|
2744
2302
|
/** @type {AST.Element} */ (element).id = /** @type {AST.Identifier} */ (
|
|
2745
2303
|
convert_from_jsx(/** @type {ESTreeJSX.JSXIdentifier} */ (open.name))
|
|
2746
2304
|
);
|
|
@@ -2768,28 +2326,26 @@ export function TSRXPlugin(config) {
|
|
|
2768
2326
|
} else if (is_fragment) {
|
|
2769
2327
|
this.#parseNativeTemplateBody(element, /** @type {AST.Element} */ (element).children, {
|
|
2770
2328
|
enterScope: true,
|
|
2329
|
+
resetFunctionBodyDepth: true,
|
|
2771
2330
|
});
|
|
2772
|
-
this.#reportDynamicJsxElementsInTsx(/** @type {AST.Element} */ (element).children);
|
|
2773
2331
|
|
|
2774
|
-
|
|
2775
|
-
this.#path.pop();
|
|
2332
|
+
this.#path.pop();
|
|
2776
2333
|
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2334
|
+
if (!element.unclosed) {
|
|
2335
|
+
const raise_error = () => {
|
|
2336
|
+
this.raise(this.start, `Expected closing tag '</>'`);
|
|
2337
|
+
};
|
|
2781
2338
|
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
}
|
|
2790
|
-
this.#popTsxTokenContextBeforeTemplateExpressionChild();
|
|
2791
|
-
this.next();
|
|
2339
|
+
this.next();
|
|
2340
|
+
if (this.value !== '/') {
|
|
2341
|
+
raise_error();
|
|
2342
|
+
}
|
|
2343
|
+
this.next();
|
|
2344
|
+
if (this.type !== tstt.jsxTagEnd) {
|
|
2345
|
+
raise_error();
|
|
2792
2346
|
}
|
|
2347
|
+
this.#popTsxTokenContextBeforeTemplateExpressionChild();
|
|
2348
|
+
this.next();
|
|
2793
2349
|
}
|
|
2794
2350
|
} else {
|
|
2795
2351
|
if (/** @type {ESTreeJSX.JSXIdentifier} */ (open.name).name === 'script') {
|
|
@@ -2862,7 +2418,7 @@ export function TSRXPlugin(config) {
|
|
|
2862
2418
|
// No closing tag
|
|
2863
2419
|
this.#report_broken_markup_error(
|
|
2864
2420
|
open.end,
|
|
2865
|
-
"Unclosed tag '<script>'. Expected '</script>' before end of
|
|
2421
|
+
"Unclosed tag '<script>'. Expected '</script>' before end of template.",
|
|
2866
2422
|
);
|
|
2867
2423
|
/** @type {AST.Element} */ (element).unclosed = true;
|
|
2868
2424
|
this.#path.pop();
|
|
@@ -2875,16 +2431,8 @@ export function TSRXPlugin(config) {
|
|
|
2875
2431
|
const end = input.indexOf('</style>');
|
|
2876
2432
|
const content = end === -1 ? input : input.slice(0, end);
|
|
2877
2433
|
|
|
2878
|
-
const component = /** @type {AST.Component} */ (
|
|
2879
|
-
this.#path.findLast((n) => n.type === 'Component')
|
|
2880
|
-
);
|
|
2881
2434
|
const parsed_css = parse_style(content, { loose: this.#loose });
|
|
2882
|
-
|
|
2883
2435
|
if (!inside_head) {
|
|
2884
|
-
if (component.css !== null) {
|
|
2885
|
-
throw new Error('Components can only have one style tag');
|
|
2886
|
-
}
|
|
2887
|
-
component.css = parsed_css;
|
|
2888
2436
|
/** @type {AST.Element} */ (element).metadata.styleScopeHash = parsed_css.hash;
|
|
2889
2437
|
}
|
|
2890
2438
|
|
|
@@ -2922,7 +2470,7 @@ export function TSRXPlugin(config) {
|
|
|
2922
2470
|
} else {
|
|
2923
2471
|
this.#report_broken_markup_error(
|
|
2924
2472
|
open.end,
|
|
2925
|
-
"Unclosed tag '<style>'. Expected '</style>' before end of
|
|
2473
|
+
"Unclosed tag '<style>'. Expected '</style>' before end of template.",
|
|
2926
2474
|
);
|
|
2927
2475
|
/** @type {AST.Element} */ (element).unclosed = true;
|
|
2928
2476
|
this.#path.pop();
|
|
@@ -2946,6 +2494,7 @@ export function TSRXPlugin(config) {
|
|
|
2946
2494
|
} else {
|
|
2947
2495
|
this.#parseNativeTemplateBody(element, /** @type {AST.Element} */ (element).children, {
|
|
2948
2496
|
enterScope: true,
|
|
2497
|
+
resetFunctionBodyDepth: true,
|
|
2949
2498
|
});
|
|
2950
2499
|
if (/** @type {AST.Tsx} */ (element).type === 'Tsx') {
|
|
2951
2500
|
this.#reportDynamicJsxElementsInTsx(/** @type {AST.Element} */ (element).children);
|
|
@@ -3014,9 +2563,10 @@ export function TSRXPlugin(config) {
|
|
|
3014
2563
|
/** @type {AST.Tsrx} */ (element).type === 'Tsrx' &&
|
|
3015
2564
|
this.#path[this.#path.length - 1] === element
|
|
3016
2565
|
) {
|
|
2566
|
+
const displayTag = element.openingElement.name ? 'tsrx' : '';
|
|
3017
2567
|
this.#report_broken_markup_error(
|
|
3018
2568
|
this.start,
|
|
3019
|
-
|
|
2569
|
+
`Unclosed tag '<${displayTag}>'. Expected '</${displayTag}>' before end of template.`,
|
|
3020
2570
|
);
|
|
3021
2571
|
element.unclosed = true;
|
|
3022
2572
|
/** @type {AST.SourceLocation} */ (element.loc).end = {
|
|
@@ -3032,7 +2582,7 @@ export function TSRXPlugin(config) {
|
|
|
3032
2582
|
const tagName = this.getElementName(element.id);
|
|
3033
2583
|
this.#report_broken_markup_error(
|
|
3034
2584
|
this.start,
|
|
3035
|
-
`Unclosed tag '<${tagName}>'. Expected '</${tagName}>' before end of
|
|
2585
|
+
`Unclosed tag '<${tagName}>'. Expected '</${tagName}>' before end of template.`,
|
|
3036
2586
|
);
|
|
3037
2587
|
element.unclosed = true;
|
|
3038
2588
|
/** @type {AST.SourceLocation} */ (element.loc).end = {
|
|
@@ -3053,13 +2603,7 @@ export function TSRXPlugin(config) {
|
|
|
3053
2603
|
}
|
|
3054
2604
|
}
|
|
3055
2605
|
|
|
3056
|
-
if (
|
|
3057
|
-
element.closingElement &&
|
|
3058
|
-
!is_tsx_compat &&
|
|
3059
|
-
!is_tsx &&
|
|
3060
|
-
!is_tsrx &&
|
|
3061
|
-
element.closingElement.name
|
|
3062
|
-
) {
|
|
2606
|
+
if (element.closingElement && !is_tsx_compat && !is_tsx && element.closingElement.name) {
|
|
3063
2607
|
/** @type {unknown} */ (element.closingElement.name) = convert_from_jsx(
|
|
3064
2608
|
element.closingElement.name,
|
|
3065
2609
|
);
|
|
@@ -3096,10 +2640,10 @@ export function TSRXPlugin(config) {
|
|
|
3096
2640
|
|
|
3097
2641
|
if (!inside_func) {
|
|
3098
2642
|
if (this.type.label === 'continue') {
|
|
3099
|
-
throw new Error('`continue` statements are not allowed in
|
|
2643
|
+
throw new Error('`continue` statements are not allowed in native templates');
|
|
3100
2644
|
}
|
|
3101
2645
|
if (this.type.label === 'break') {
|
|
3102
|
-
throw new Error('`break` statements are not allowed in
|
|
2646
|
+
throw new Error('`break` statements are not allowed in native templates');
|
|
3103
2647
|
}
|
|
3104
2648
|
}
|
|
3105
2649
|
|
|
@@ -3110,6 +2654,16 @@ export function TSRXPlugin(config) {
|
|
|
3110
2654
|
);
|
|
3111
2655
|
return;
|
|
3112
2656
|
}
|
|
2657
|
+
if (
|
|
2658
|
+
current_template_node?.type === 'Tsrx' &&
|
|
2659
|
+
!current_template_node.openingElement.name &&
|
|
2660
|
+
((this.type === tstt.jsxTagStart && this.input.slice(this.pos, this.pos + 2) === '/>') ||
|
|
2661
|
+
(this.input.charCodeAt(this.start) === CharCode.lessThan &&
|
|
2662
|
+
this.input.slice(this.start + 1, this.start + 3) === '/>'))
|
|
2663
|
+
) {
|
|
2664
|
+
this.exprAllowed = false;
|
|
2665
|
+
return;
|
|
2666
|
+
}
|
|
3113
2667
|
if (this.type === tt.braceL) {
|
|
3114
2668
|
body.push(this.#parseNativeTemplateExpressionContainer());
|
|
3115
2669
|
} else if (
|
|
@@ -3118,7 +2672,7 @@ export function TSRXPlugin(config) {
|
|
|
3118
2672
|
) {
|
|
3119
2673
|
body.push(this.parseDoubleQuotedTextChild());
|
|
3120
2674
|
} else if (this.type === tt.braceR) {
|
|
3121
|
-
// Leaving a
|
|
2675
|
+
// Leaving a native template body. We may still be in TSX/JSX tokenization
|
|
3122
2676
|
// context (e.g. after parsing markup), but the closing `}` is a JS token.
|
|
3123
2677
|
// If we don't reset this here, the following `next()` can read EOF using
|
|
3124
2678
|
// `jsx_readToken()` and throw "Unterminated JSX contents".
|
|
@@ -3136,8 +2690,8 @@ export function TSRXPlugin(config) {
|
|
|
3136
2690
|
if (this.type === tstt.jsxTagStart) {
|
|
3137
2691
|
this.next();
|
|
3138
2692
|
} else {
|
|
3139
|
-
// A control-flow block inside
|
|
3140
|
-
// in normal JS mode, so
|
|
2693
|
+
// A control-flow block inside a native template can leave the tokenizer
|
|
2694
|
+
// in normal JS mode, so a closing tag may arrive as a relational
|
|
3141
2695
|
// `<` token. Re-enter JSX closing-tag parsing manually.
|
|
3142
2696
|
this.pos = startPos + 1;
|
|
3143
2697
|
this.type = tstt.jsxTagStart;
|
|
@@ -3186,7 +2740,7 @@ export function TSRXPlugin(config) {
|
|
|
3186
2740
|
? closingElement.name.namespace.name + ':' + closingElement.name.name.name
|
|
3187
2741
|
: this.getElementName(closingElement.name);
|
|
3188
2742
|
} else if (currentElement.type === 'Tsrx') {
|
|
3189
|
-
openingTagName = '
|
|
2743
|
+
openingTagName = '';
|
|
3190
2744
|
closingTagName =
|
|
3191
2745
|
closingElement.name?.type === 'JSXNamespacedName'
|
|
3192
2746
|
? closingElement.name.namespace.name + ':' + closingElement.name.name.name
|
|
@@ -3212,7 +2766,7 @@ export function TSRXPlugin(config) {
|
|
|
3212
2766
|
while (this.#path.length > 0) {
|
|
3213
2767
|
const elem = this.#path[this.#path.length - 1];
|
|
3214
2768
|
|
|
3215
|
-
// Stop at non-
|
|
2769
|
+
// Stop at non-template boundaries.
|
|
3216
2770
|
if (
|
|
3217
2771
|
elem.type !== 'Element' &&
|
|
3218
2772
|
elem.type !== 'Tsx' &&
|
|
@@ -3230,7 +2784,7 @@ export function TSRXPlugin(config) {
|
|
|
3230
2784
|
? 'tsx'
|
|
3231
2785
|
: null
|
|
3232
2786
|
: elem.type === 'Tsrx'
|
|
3233
|
-
? '
|
|
2787
|
+
? ''
|
|
3234
2788
|
: elem.id
|
|
3235
2789
|
? this.getElementName(elem.id)
|
|
3236
2790
|
: null;
|
|
@@ -3258,7 +2812,7 @@ export function TSRXPlugin(config) {
|
|
|
3258
2812
|
) {
|
|
3259
2813
|
const elementToCloseName =
|
|
3260
2814
|
elementToClose.type === 'Tsrx'
|
|
3261
|
-
? '
|
|
2815
|
+
? ''
|
|
3262
2816
|
: /** @type {AST.Element} */ (elementToClose).id
|
|
3263
2817
|
? this.getElementName(/** @type {AST.Element} */ (elementToClose).id)
|
|
3264
2818
|
: null;
|
|
@@ -3279,6 +2833,7 @@ export function TSRXPlugin(config) {
|
|
|
3279
2833
|
} else {
|
|
3280
2834
|
skipWhitespace(this);
|
|
3281
2835
|
const node = this.parseStatement(null);
|
|
2836
|
+
this.#report_invalid_template_return_statements(node);
|
|
3282
2837
|
body.push(node);
|
|
3283
2838
|
|
|
3284
2839
|
// Ensure we're not in JSX context before recursing
|
|
@@ -3363,16 +2918,11 @@ export function TSRXPlugin(config) {
|
|
|
3363
2918
|
this.type === tt.braceL &&
|
|
3364
2919
|
this.context.some((c) => c === tstc.tc_expr)
|
|
3365
2920
|
) {
|
|
3366
|
-
return /** @type {ESTreeJSX.JSXEmptyExpression | AST.TSRXExpression | AST.
|
|
2921
|
+
return /** @type {ESTreeJSX.JSXEmptyExpression | AST.TSRXExpression | AST.TextNode | ESTreeJSX.JSXExpressionContainer} */ (
|
|
3367
2922
|
/** @type {unknown} */ (this.#parseNativeTemplateExpressionContainer())
|
|
3368
2923
|
);
|
|
3369
2924
|
}
|
|
3370
2925
|
|
|
3371
|
-
if (this.value === 'component') {
|
|
3372
|
-
this.awaitPos = 0;
|
|
3373
|
-
return this.parseComponent({ requireName: true, declareName: true });
|
|
3374
|
-
}
|
|
3375
|
-
|
|
3376
2926
|
if (this.type === tstt.jsxTagStart) {
|
|
3377
2927
|
this.next();
|
|
3378
2928
|
if (this.value === '/') {
|
|
@@ -3392,18 +2942,6 @@ export function TSRXPlugin(config) {
|
|
|
3392
2942
|
this.context.pop();
|
|
3393
2943
|
}
|
|
3394
2944
|
}
|
|
3395
|
-
const context_restore = this.#functionBodyContextRestoreStack.at(-1);
|
|
3396
|
-
if (
|
|
3397
|
-
this.#functionBodyDepth > 0 &&
|
|
3398
|
-
node.type === 'Tsrx' &&
|
|
3399
|
-
context_restore?.canRestore &&
|
|
3400
|
-
this.type !== tt.braceR &&
|
|
3401
|
-
this.type !== tt.comma
|
|
3402
|
-
) {
|
|
3403
|
-
context_restore.restore = true;
|
|
3404
|
-
this.context = [b_stat];
|
|
3405
|
-
this.exprAllowed = true;
|
|
3406
|
-
}
|
|
3407
2945
|
return node;
|
|
3408
2946
|
}
|
|
3409
2947
|
|
|
@@ -3411,7 +2949,7 @@ export function TSRXPlugin(config) {
|
|
|
3411
2949
|
this.#functionBodyDepth === 0 &&
|
|
3412
2950
|
this.type === tt.string &&
|
|
3413
2951
|
this.input.charCodeAt(this.start) === CharCode.doubleQuote &&
|
|
3414
|
-
(this.#path.at(-1)?.type === '
|
|
2952
|
+
(this.#path.at(-1)?.type === 'Element' || this.#path.at(-1)?.type === 'Tsrx')
|
|
3415
2953
|
) {
|
|
3416
2954
|
this.pos = this.start;
|
|
3417
2955
|
this.#readDoubleQuotedTextChildToken();
|
|
@@ -3461,11 +2999,11 @@ export function TSRXPlugin(config) {
|
|
|
3461
2999
|
const parent = this.#path.at(-1);
|
|
3462
3000
|
|
|
3463
3001
|
// Inside a JS function body, parse `{...}` as a regular block statement,
|
|
3464
|
-
// even if the nearest `#path` entry is a
|
|
3002
|
+
// even if the nearest `#path` entry is a native template — we're in a
|
|
3465
3003
|
// nested function callable, not in a template.
|
|
3466
3004
|
if (
|
|
3467
3005
|
this.#functionBodyDepth === 0 &&
|
|
3468
|
-
(parent?.type === '
|
|
3006
|
+
(parent?.type === 'Element' || parent?.type === 'Tsrx')
|
|
3469
3007
|
) {
|
|
3470
3008
|
if (createNewLexicalScope === void 0) createNewLexicalScope = true;
|
|
3471
3009
|
if (node === void 0) node = /** @type {AST.BlockStatement} */ (this.startNode());
|