@tsrx/core 0.1.15 → 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 +248 -635
- 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,11 @@ 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
|
|
21
|
-
'
|
|
22
|
-
const
|
|
23
|
-
'
|
|
19
|
+
import { TSRX_RETURN_STATEMENT_ERROR } from './analyze/validation.js';
|
|
20
|
+
const DYNAMIC_ELEMENT_IN_TSX_ERROR =
|
|
21
|
+
'Dynamic element syntax (`<@...>`) is only supported in native TSRX templates.';
|
|
22
|
+
const DYNAMIC_ATTRIBUTE_NAME_ERROR =
|
|
23
|
+
'Dynamic component / element syntax (`@`) is only supported on native TSRX element names, not attribute names.';
|
|
24
24
|
|
|
25
25
|
const CharCode = Object.freeze({
|
|
26
26
|
tab: 9,
|
|
@@ -201,44 +201,9 @@ function looks_like_generic_arrow(input, pos) {
|
|
|
201
201
|
);
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
-
/**
|
|
205
|
-
* @param {AST.Node | null | undefined} node
|
|
206
|
-
* @returns {boolean}
|
|
207
|
-
*/
|
|
208
|
-
function is_pascal_case_function(node) {
|
|
209
|
-
if (node && 'id' in node && node.id && node.id.type === 'Identifier') {
|
|
210
|
-
return /^[A-Z]/.test(node.id.name);
|
|
211
|
-
}
|
|
212
|
-
return false;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* @param {string} input
|
|
217
|
-
* @param {number} pos
|
|
218
|
-
*/
|
|
219
|
-
function previous_word_before(input, pos) {
|
|
220
|
-
let i = pos - 1;
|
|
221
|
-
while (i >= 0) {
|
|
222
|
-
const ch = input.charCodeAt(i);
|
|
223
|
-
if (
|
|
224
|
-
ch !== CharCode.space &&
|
|
225
|
-
ch !== CharCode.tab &&
|
|
226
|
-
ch !== CharCode.lineFeed &&
|
|
227
|
-
ch !== CharCode.carriageReturn
|
|
228
|
-
)
|
|
229
|
-
break;
|
|
230
|
-
i--;
|
|
231
|
-
}
|
|
232
|
-
const end = i + 1;
|
|
233
|
-
while (i >= 0 && /[$_\p{ID_Continue}]/u.test(input[i])) {
|
|
234
|
-
i--;
|
|
235
|
-
}
|
|
236
|
-
return input.slice(i + 1, end);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
204
|
/**
|
|
240
205
|
* Acorn parser plugin for Ripple syntax extensions.
|
|
241
|
-
* Adds support for:
|
|
206
|
+
* Adds support for: native TSRX templates, &[]/&{} lazy destructuring,
|
|
242
207
|
* submodule imports, TSRX directives, and enhanced JSX handling.
|
|
243
208
|
*
|
|
244
209
|
* @param {import('../types/index').TSRXPluginConfig} [config] - Plugin configuration
|
|
@@ -264,18 +229,14 @@ export function TSRXPlugin(config) {
|
|
|
264
229
|
#commentContextId = 0;
|
|
265
230
|
#collect = false;
|
|
266
231
|
#loose = false;
|
|
267
|
-
/** @type {AST.Node[]} */
|
|
268
|
-
#functionStack = [];
|
|
269
|
-
/** @type {Array<{ parentContext: any[], canRestore: boolean, restore: boolean }>} */
|
|
270
|
-
#functionBodyContextRestoreStack = [];
|
|
271
232
|
/** @type {import('../types/index').CompileError[] | undefined} */
|
|
272
233
|
#errors = undefined;
|
|
273
234
|
/** @type {string | null} */
|
|
274
235
|
#filename = null;
|
|
275
|
-
#componentDepth = 0;
|
|
276
236
|
#functionBodyDepth = 0;
|
|
277
237
|
#allowExpressionContainerTrailingSemicolon = false;
|
|
278
238
|
#tsxIslandExpressionDepth = 0;
|
|
239
|
+
#jsxAttributeValueExpressionDepth = 0;
|
|
279
240
|
|
|
280
241
|
/**
|
|
281
242
|
* @type {Parse.Parser['finishNode']}
|
|
@@ -330,16 +291,8 @@ export function TSRXPlugin(config) {
|
|
|
330
291
|
return null;
|
|
331
292
|
}
|
|
332
293
|
|
|
333
|
-
#isInsideComponent() {
|
|
334
|
-
return this.#componentDepth > 0;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
#isInsideComponentTemplate() {
|
|
338
|
-
return this.#isInsideComponent() && this.#functionBodyDepth === 0;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
294
|
/**
|
|
342
|
-
*
|
|
295
|
+
* Native TSRX template bodies share one grammar across elements and fragments.
|
|
343
296
|
* This helper keeps the parser-state setup in one place while callers keep
|
|
344
297
|
* ownership of their distinct closing delimiter handling (`}` vs `</tag>`).
|
|
345
298
|
*
|
|
@@ -348,19 +301,13 @@ export function TSRXPlugin(config) {
|
|
|
348
301
|
* @param {{
|
|
349
302
|
* enterScope?: boolean,
|
|
350
303
|
* pushPath?: boolean,
|
|
351
|
-
* trackComponentDepth?: boolean,
|
|
352
304
|
* resetFunctionBodyDepth?: boolean,
|
|
353
305
|
* }} [options]
|
|
354
306
|
*/
|
|
355
307
|
#parseNativeTemplateBody(
|
|
356
308
|
node,
|
|
357
309
|
body,
|
|
358
|
-
{
|
|
359
|
-
enterScope = false,
|
|
360
|
-
pushPath = false,
|
|
361
|
-
trackComponentDepth = false,
|
|
362
|
-
resetFunctionBodyDepth = false,
|
|
363
|
-
} = {},
|
|
310
|
+
{ enterScope = false, pushPath = false, resetFunctionBodyDepth = false } = {},
|
|
364
311
|
) {
|
|
365
312
|
const parent_function_body_depth = this.#functionBodyDepth;
|
|
366
313
|
|
|
@@ -373,16 +320,10 @@ export function TSRXPlugin(config) {
|
|
|
373
320
|
if (pushPath) {
|
|
374
321
|
this.#path.push(node);
|
|
375
322
|
}
|
|
376
|
-
if (trackComponentDepth) {
|
|
377
|
-
this.#componentDepth++;
|
|
378
|
-
}
|
|
379
323
|
|
|
380
324
|
try {
|
|
381
325
|
this.parseTemplateBody(body);
|
|
382
326
|
} finally {
|
|
383
|
-
if (trackComponentDepth) {
|
|
384
|
-
this.#componentDepth--;
|
|
385
|
-
}
|
|
386
327
|
if (pushPath) {
|
|
387
328
|
this.#path.pop();
|
|
388
329
|
}
|
|
@@ -400,7 +341,6 @@ export function TSRXPlugin(config) {
|
|
|
400
341
|
*/
|
|
401
342
|
#isNativeTemplateNode(node) {
|
|
402
343
|
return (
|
|
403
|
-
node?.type === 'Component' ||
|
|
404
344
|
node?.type === 'Element' ||
|
|
405
345
|
node?.type === 'Tsx' ||
|
|
406
346
|
node?.type === 'Tsrx' ||
|
|
@@ -408,6 +348,32 @@ export function TSRXPlugin(config) {
|
|
|
408
348
|
);
|
|
409
349
|
}
|
|
410
350
|
|
|
351
|
+
/**
|
|
352
|
+
* @param {AST.Node[]} children
|
|
353
|
+
*/
|
|
354
|
+
#reportDynamicJsxElementsInTsx(children) {
|
|
355
|
+
for (const child of children) {
|
|
356
|
+
if (child?.type === 'JSXElement') {
|
|
357
|
+
const name = child.openingElement?.name;
|
|
358
|
+
const is_dynamic_name =
|
|
359
|
+
(name?.type === 'JSXIdentifier' && name.tracked) ||
|
|
360
|
+
(name?.type === 'JSXMemberExpression' &&
|
|
361
|
+
name.object.type === 'JSXIdentifier' &&
|
|
362
|
+
name.object.tracked);
|
|
363
|
+
if (is_dynamic_name) {
|
|
364
|
+
this.#report_recoverable_error_range(
|
|
365
|
+
/** @type {AST.NodeWithLocation} */ (name).start ?? child.start,
|
|
366
|
+
/** @type {AST.NodeWithLocation} */ (name).end ?? child.end,
|
|
367
|
+
DYNAMIC_ELEMENT_IN_TSX_ERROR,
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
this.#reportDynamicJsxElementsInTsx(/** @type {AST.Node[]} */ (child.children));
|
|
371
|
+
} else if (child?.type === 'Tsx' || child?.type === 'TsxCompat') {
|
|
372
|
+
this.#reportDynamicJsxElementsInTsx(/** @type {AST.Node[]} */ (child.children));
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
411
377
|
#parseNativeTemplateExpressionContainer() {
|
|
412
378
|
const allow_trailing_semicolon = this.#allowExpressionContainerTrailingSemicolon;
|
|
413
379
|
this.#allowExpressionContainerTrailingSemicolon = true;
|
|
@@ -420,26 +386,11 @@ export function TSRXPlugin(config) {
|
|
|
420
386
|
// Keep JSXEmptyExpression as-is (for prettier to handle comments)
|
|
421
387
|
// but convert other expressions to native TSRX child nodes.
|
|
422
388
|
if (node.expression.type !== 'JSXEmptyExpression') {
|
|
423
|
-
/** @type {AST.TSRXExpression | AST.
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
? 'Text'
|
|
429
|
-
: node.style
|
|
430
|
-
? 'Style'
|
|
431
|
-
: 'TSRXExpression';
|
|
432
|
-
if (node.style) {
|
|
433
|
-
/** @type {AST.Style} */ (/** @type {unknown} */ (node)).value =
|
|
434
|
-
/** @type {AST.Literal} */ (node.expression);
|
|
435
|
-
delete (/** @type {any} */ (node).expression);
|
|
436
|
-
}
|
|
437
|
-
delete node.html;
|
|
438
|
-
delete node.text;
|
|
439
|
-
delete node.style;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
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} */ (
|
|
443
394
|
/** @type {unknown} */ (node)
|
|
444
395
|
);
|
|
445
396
|
}
|
|
@@ -463,7 +414,7 @@ export function TSRXPlugin(config) {
|
|
|
463
414
|
const displayTag = tagName || '';
|
|
464
415
|
this.#report_broken_markup_error(
|
|
465
416
|
this.start,
|
|
466
|
-
`Unclosed tag '<${displayTag}>'. Expected '</${displayTag}>' before end of
|
|
417
|
+
`Unclosed tag '<${displayTag}>'. Expected '</${displayTag}>' before end of template.`,
|
|
467
418
|
);
|
|
468
419
|
island.unclosed = true;
|
|
469
420
|
/** @type {AST.NodeWithLocation} */ (island).loc.end = {
|
|
@@ -549,25 +500,16 @@ export function TSRXPlugin(config) {
|
|
|
549
500
|
*/
|
|
550
501
|
#isReservedTemplateTagNameStart(index) {
|
|
551
502
|
const char_after_tsx = this.input.charCodeAt(index + 3);
|
|
552
|
-
const char_after_tsrx = this.input.charCodeAt(index + 4);
|
|
553
503
|
return (
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
(this.input.startsWith('tsrx', index) &&
|
|
564
|
-
(index + 4 >= this.input.length ||
|
|
565
|
-
char_after_tsrx === CharCode.greaterThan ||
|
|
566
|
-
char_after_tsrx === CharCode.slash ||
|
|
567
|
-
char_after_tsrx === CharCode.space ||
|
|
568
|
-
char_after_tsrx === CharCode.tab ||
|
|
569
|
-
char_after_tsrx === CharCode.lineFeed ||
|
|
570
|
-
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)
|
|
571
513
|
);
|
|
572
514
|
}
|
|
573
515
|
|
|
@@ -599,7 +541,7 @@ export function TSRXPlugin(config) {
|
|
|
599
541
|
while (this.pos < this.input.length) {
|
|
600
542
|
const ch = this.input.charCodeAt(this.pos);
|
|
601
543
|
|
|
602
|
-
// Stop at opening tag, expression, or the
|
|
544
|
+
// Stop at opening tag, expression, or the template-closing brace
|
|
603
545
|
if (ch === CharCode.lessThan || ch === CharCode.openBrace || ch === CharCode.closeBrace) {
|
|
604
546
|
break;
|
|
605
547
|
}
|
|
@@ -791,13 +733,13 @@ export function TSRXPlugin(config) {
|
|
|
791
733
|
}
|
|
792
734
|
}
|
|
793
735
|
|
|
794
|
-
// Inside
|
|
736
|
+
// Inside a native template JSX expression container — strip
|
|
795
737
|
// both the leaked `b_stat` and the container's `tc_expr`.
|
|
796
738
|
if (top === b_stat && second === tstc.tc_expr) {
|
|
797
739
|
ctx.length = ci - 1;
|
|
798
740
|
return;
|
|
799
741
|
}
|
|
800
|
-
// Statement-bodied
|
|
742
|
+
// Statement-bodied native template attributes can leave the attribute's
|
|
801
743
|
// expression contexts above the still-open JSX tag context. Strip
|
|
802
744
|
// those so a following `/>` stays in JSX opening-tag mode.
|
|
803
745
|
if (
|
|
@@ -835,10 +777,7 @@ export function TSRXPlugin(config) {
|
|
|
835
777
|
}
|
|
836
778
|
|
|
837
779
|
const parent = this.#path.at(-1);
|
|
838
|
-
if (
|
|
839
|
-
!parent ||
|
|
840
|
-
(parent.type !== 'Component' && parent.type !== 'Element' && parent.type !== 'Tsrx')
|
|
841
|
-
) {
|
|
780
|
+
if (!parent || (parent.type !== 'Element' && parent.type !== 'Tsrx')) {
|
|
842
781
|
return false;
|
|
843
782
|
}
|
|
844
783
|
|
|
@@ -945,6 +884,75 @@ export function TSRXPlugin(config) {
|
|
|
945
884
|
this.raise(position, message);
|
|
946
885
|
}
|
|
947
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
|
+
|
|
948
956
|
/**
|
|
949
957
|
* When collecting, keep parsing after duplicate declaration diagnostics so
|
|
950
958
|
* editor tooling can continue producing AST and mappings.
|
|
@@ -1014,64 +1022,6 @@ export function TSRXPlugin(config) {
|
|
|
1014
1022
|
}
|
|
1015
1023
|
}
|
|
1016
1024
|
|
|
1017
|
-
/**
|
|
1018
|
-
* Override parseProperty to support component methods in object literals.
|
|
1019
|
-
* Handles syntax like `{ component something() { <div /> } }`
|
|
1020
|
-
* Also supports computed names: `{ component ['something']() { <div /> } }`
|
|
1021
|
-
* @type {Parse.Parser['parseProperty']}
|
|
1022
|
-
*/
|
|
1023
|
-
parseProperty(isPattern, refDestructuringErrors) {
|
|
1024
|
-
// Check if this is a component method: component name( ... ) { ... }
|
|
1025
|
-
if (!isPattern && this.type === tt.name && this.value === 'component') {
|
|
1026
|
-
// Look ahead to see if this is "component identifier(", "component identifier<", "component [", or "component 'string'"
|
|
1027
|
-
const lookahead = this.input.slice(this.pos).match(/^\s*(?:(\w+)\s*[(<]|\[|['"])/);
|
|
1028
|
-
if (lookahead) {
|
|
1029
|
-
// This is a component method definition
|
|
1030
|
-
const prop = /** @type {AST.Property} */ (this.startNode());
|
|
1031
|
-
const isComputed = lookahead[0].trim().startsWith('[');
|
|
1032
|
-
const isStringLiteral = /^['"]/.test(lookahead[0].trim());
|
|
1033
|
-
|
|
1034
|
-
if (isComputed) {
|
|
1035
|
-
// For computed names, consume 'component'
|
|
1036
|
-
// parse the key, then parse component without name
|
|
1037
|
-
this.next(); // consume 'component'
|
|
1038
|
-
this.next(); // consume '['
|
|
1039
|
-
prop.key = this.parseExpression();
|
|
1040
|
-
this.expect(tt.bracketR);
|
|
1041
|
-
prop.computed = true;
|
|
1042
|
-
|
|
1043
|
-
// Parse component without name (skipName: true)
|
|
1044
|
-
const component_node = this.parseComponent({ skipName: true });
|
|
1045
|
-
/** @type {AST.TSRXProperty} */ (prop).value = component_node;
|
|
1046
|
-
} else if (isStringLiteral) {
|
|
1047
|
-
// For string literal names, consume 'component'
|
|
1048
|
-
// parse the string key, then parse component without name
|
|
1049
|
-
this.next(); // consume 'component'
|
|
1050
|
-
prop.key = /** @type {AST.Literal} */ (this.parseExprAtom());
|
|
1051
|
-
prop.computed = false;
|
|
1052
|
-
|
|
1053
|
-
// Parse component without name (skipName: true)
|
|
1054
|
-
const component_node = this.parseComponent({ skipName: true });
|
|
1055
|
-
/** @type {AST.TSRXProperty} */ (prop).value = component_node;
|
|
1056
|
-
} else {
|
|
1057
|
-
const component_node = this.parseComponent({ requireName: true });
|
|
1058
|
-
|
|
1059
|
-
prop.key = /** @type {AST.Identifier} */ (component_node.id);
|
|
1060
|
-
/** @type {AST.TSRXProperty} */ (prop).value = component_node;
|
|
1061
|
-
prop.computed = false;
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
prop.shorthand = false;
|
|
1065
|
-
prop.method = true;
|
|
1066
|
-
prop.kind = 'init';
|
|
1067
|
-
|
|
1068
|
-
return this.finishNode(prop, 'Property');
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
return super.parseProperty(isPattern, refDestructuringErrors);
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
1025
|
/**
|
|
1076
1026
|
* Override parsePropertyValue to support TypeScript generic methods in object literals.
|
|
1077
1027
|
* By default, acorn-typescript doesn't handle `{ method<T>() {} }` syntax.
|
|
@@ -1211,16 +1161,17 @@ export function TSRXPlugin(config) {
|
|
|
1211
1161
|
* @type {Parse.Parser['getTokenFromCode']}
|
|
1212
1162
|
*/
|
|
1213
1163
|
getTokenFromCode(code) {
|
|
1214
|
-
// Callback props that return
|
|
1164
|
+
// Callback props that return native templates without a semicolon can
|
|
1215
1165
|
// leave the attribute expression context above the still-open tag. Drop
|
|
1216
1166
|
// it before tokenizing `/>`, otherwise Acorn treats `/` as a regexp.
|
|
1217
1167
|
if (
|
|
1218
1168
|
code === CharCode.slash &&
|
|
1219
1169
|
this.input.charCodeAt(this.pos + 1) === CharCode.greaterThan &&
|
|
1220
|
-
this.
|
|
1221
|
-
this.context[this.context.length - 2] === tstc.tc_oTag
|
|
1170
|
+
this.context.includes(tstc.tc_oTag)
|
|
1222
1171
|
) {
|
|
1223
|
-
this.context.
|
|
1172
|
+
while (this.context.length > 0 && this.curContext() !== tstc.tc_oTag) {
|
|
1173
|
+
this.context.pop();
|
|
1174
|
+
}
|
|
1224
1175
|
this.exprAllowed = false;
|
|
1225
1176
|
}
|
|
1226
1177
|
if (code === CharCode.doubleQuote) {
|
|
@@ -1239,7 +1190,10 @@ export function TSRXPlugin(config) {
|
|
|
1239
1190
|
|
|
1240
1191
|
if (code === CharCode.lessThan) {
|
|
1241
1192
|
// < character
|
|
1242
|
-
const
|
|
1193
|
+
const parent = this.#path.at(-1);
|
|
1194
|
+
const inNativeTemplate =
|
|
1195
|
+
this.#functionBodyDepth === 0 &&
|
|
1196
|
+
(parent?.type === 'Element' || parent?.type === 'Tsrx');
|
|
1243
1197
|
/** @type {number | null} */
|
|
1244
1198
|
let prevNonWhitespaceChar = null;
|
|
1245
1199
|
|
|
@@ -1278,7 +1232,7 @@ export function TSRXPlugin(config) {
|
|
|
1278
1232
|
}
|
|
1279
1233
|
}
|
|
1280
1234
|
|
|
1281
|
-
// Support parsing standalone template markup at the top-level
|
|
1235
|
+
// Support parsing standalone template markup at the top-level
|
|
1282
1236
|
// for tooling like Prettier, e.g.:
|
|
1283
1237
|
// <Something>...</Something>\n\n<Child />
|
|
1284
1238
|
// <head><style>...</style></head>
|
|
@@ -1307,13 +1261,13 @@ export function TSRXPlugin(config) {
|
|
|
1307
1261
|
prevNonWhitespaceChar === CharCode.closeBrace ||
|
|
1308
1262
|
prevNonWhitespaceChar === CharCode.greaterThan;
|
|
1309
1263
|
|
|
1310
|
-
if (!
|
|
1264
|
+
if (!inNativeTemplate && prevAllowsTagStart && isTagLikeAfterLt) {
|
|
1311
1265
|
++this.pos;
|
|
1312
1266
|
return this.finishToken(tstt.jsxTagStart);
|
|
1313
1267
|
}
|
|
1314
1268
|
|
|
1315
|
-
if (
|
|
1316
|
-
// Inside
|
|
1269
|
+
if (inNativeTemplate) {
|
|
1270
|
+
// Inside native template bodies, allow adjacent tags without requiring
|
|
1317
1271
|
// a newline/indentation before the next '<'. This is important for inputs
|
|
1318
1272
|
// like `<div />` and `</div><style>...</style>` which Prettier formats.
|
|
1319
1273
|
if (
|
|
@@ -1452,36 +1406,6 @@ export function TSRXPlugin(config) {
|
|
|
1452
1406
|
return super.checkLValSimple(expr, bindingType, checkClashes);
|
|
1453
1407
|
}
|
|
1454
1408
|
|
|
1455
|
-
/**
|
|
1456
|
-
* Components do not use Acorn's normal function-body parser, but they
|
|
1457
|
-
* should still report duplicate parameter names like functions do. Keep
|
|
1458
|
-
* this validation on `BIND_OUTSIDE` so params are checked without being
|
|
1459
|
-
* declared in the component template scope, preserving existing shadowing
|
|
1460
|
-
* behavior.
|
|
1461
|
-
*
|
|
1462
|
-
* @param {AST.Pattern[]} params
|
|
1463
|
-
*/
|
|
1464
|
-
checkComponentParams(params) {
|
|
1465
|
-
/** @type {Record<string, boolean>} */
|
|
1466
|
-
const name_hash = Object.create(null);
|
|
1467
|
-
for (const param of params || []) {
|
|
1468
|
-
this.checkLValInnerPattern(param, BINDING_TYPES.BIND_OUTSIDE, name_hash);
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
/**
|
|
1473
|
-
* Parse expression atom - handles RippleArray and RippleObject literals
|
|
1474
|
-
* @type {Parse.Parser['parseExprAtom']}
|
|
1475
|
-
*/
|
|
1476
|
-
parseExprAtom(refDestructuringErrors, forNew, forInit) {
|
|
1477
|
-
// Check if this is a component expression (e.g., in object literal values)
|
|
1478
|
-
if (this.type === tt.name && this.value === 'component') {
|
|
1479
|
-
return this.parseComponent();
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
return super.parseExprAtom(refDestructuringErrors, forNew, forInit);
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
1409
|
/**
|
|
1486
1410
|
* Override to track parenthesized expressions in metadata
|
|
1487
1411
|
* This allows the prettier plugin to preserve parentheses where they existed
|
|
@@ -1524,108 +1448,6 @@ export function TSRXPlugin(config) {
|
|
|
1524
1448
|
this.undefinedExports[name] = id;
|
|
1525
1449
|
}
|
|
1526
1450
|
|
|
1527
|
-
/**
|
|
1528
|
-
* Parse a component - common implementation used by statements, expressions, and export defaults
|
|
1529
|
-
* @type {Parse.Parser['parseComponent']}
|
|
1530
|
-
*/
|
|
1531
|
-
parseComponent({
|
|
1532
|
-
requireName = false,
|
|
1533
|
-
isDefault = false,
|
|
1534
|
-
declareName = false,
|
|
1535
|
-
skipName = false,
|
|
1536
|
-
} = {}) {
|
|
1537
|
-
const node = /** @type {AST.Component} */ (this.startNode());
|
|
1538
|
-
const parent_context = [...this.context];
|
|
1539
|
-
const restore_parent_context =
|
|
1540
|
-
!requireName &&
|
|
1541
|
-
this.#isInsideComponent() &&
|
|
1542
|
-
this.context.some((context) => context === tstc.tc_oTag || context === tstc.tc_cTag);
|
|
1543
|
-
node.type = 'Component';
|
|
1544
|
-
node.css = null;
|
|
1545
|
-
node.default = isDefault;
|
|
1546
|
-
|
|
1547
|
-
// skipName is used for computed property names where 'component' and the key
|
|
1548
|
-
// have already been consumed before calling parseComponent
|
|
1549
|
-
if (!skipName) {
|
|
1550
|
-
this.next(); // consume 'component'
|
|
1551
|
-
}
|
|
1552
|
-
this.enterScope(0);
|
|
1553
|
-
|
|
1554
|
-
if (skipName) {
|
|
1555
|
-
// For computed names, the key is parsed separately, so id is null
|
|
1556
|
-
node.id = null;
|
|
1557
|
-
} else if (requireName) {
|
|
1558
|
-
node.id = this.parseIdent();
|
|
1559
|
-
if (declareName) {
|
|
1560
|
-
this.declareName(
|
|
1561
|
-
node.id.name,
|
|
1562
|
-
BINDING_TYPES.BIND_FUNCTION,
|
|
1563
|
-
/** @type {AST.NodeWithLocation} */ (node.id).start,
|
|
1564
|
-
);
|
|
1565
|
-
}
|
|
1566
|
-
} else {
|
|
1567
|
-
node.id = this.type.label === 'name' ? this.parseIdent() : null;
|
|
1568
|
-
if (declareName && node.id) {
|
|
1569
|
-
this.declareName(
|
|
1570
|
-
node.id.name,
|
|
1571
|
-
BINDING_TYPES.BIND_FUNCTION,
|
|
1572
|
-
/** @type {AST.NodeWithLocation} */ (node.id).start,
|
|
1573
|
-
);
|
|
1574
|
-
}
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
this.parseFunctionParams(node);
|
|
1578
|
-
this.checkComponentParams(node.params);
|
|
1579
|
-
|
|
1580
|
-
const is_arrow_component = this.type === tt.arrow;
|
|
1581
|
-
if (is_arrow_component) {
|
|
1582
|
-
if (node.id || requireName || skipName) {
|
|
1583
|
-
this.raise(
|
|
1584
|
-
this.start,
|
|
1585
|
-
'Arrow component syntax is only supported for anonymous component expressions.',
|
|
1586
|
-
);
|
|
1587
|
-
}
|
|
1588
|
-
node.metadata ??= { path: [] };
|
|
1589
|
-
node.metadata.arrow = true;
|
|
1590
|
-
this.next();
|
|
1591
|
-
}
|
|
1592
|
-
|
|
1593
|
-
if (this.type === tt.braceL) {
|
|
1594
|
-
this.#allowDoubleQuotedTextChildAfterBrace = true;
|
|
1595
|
-
}
|
|
1596
|
-
this.eat(tt.braceL);
|
|
1597
|
-
node.body = [];
|
|
1598
|
-
this.#parseNativeTemplateBody(node, node.body, {
|
|
1599
|
-
pushPath: true,
|
|
1600
|
-
trackComponentDepth: true,
|
|
1601
|
-
resetFunctionBodyDepth: true,
|
|
1602
|
-
});
|
|
1603
|
-
this.exitScope();
|
|
1604
|
-
|
|
1605
|
-
this.next();
|
|
1606
|
-
skipWhitespace(this);
|
|
1607
|
-
if (restore_parent_context) {
|
|
1608
|
-
this.context = this.type === tt.braceR ? parent_context.slice(0, -1) : parent_context;
|
|
1609
|
-
this.exprAllowed = false;
|
|
1610
|
-
}
|
|
1611
|
-
this.finishNode(node, 'Component');
|
|
1612
|
-
this.awaitPos = 0;
|
|
1613
|
-
|
|
1614
|
-
return node;
|
|
1615
|
-
}
|
|
1616
|
-
|
|
1617
|
-
/**
|
|
1618
|
-
* @type {Parse.Parser['parseExportDefaultDeclaration']}
|
|
1619
|
-
*/
|
|
1620
|
-
parseExportDefaultDeclaration() {
|
|
1621
|
-
// Check if this is "export default component"
|
|
1622
|
-
if (this.value === 'component') {
|
|
1623
|
-
return this.parseComponent({ isDefault: true });
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
return super.parseExportDefaultDeclaration();
|
|
1627
|
-
}
|
|
1628
|
-
|
|
1629
1451
|
/** @type {Parse.Parser['parseForStatement']} */
|
|
1630
1452
|
parseForStatement(node) {
|
|
1631
1453
|
this.next();
|
|
@@ -1824,77 +1646,13 @@ export function TSRXPlugin(config) {
|
|
|
1824
1646
|
*/
|
|
1825
1647
|
parseFunctionBody(node, isArrowFunction, isMethod, forInit, ...args) {
|
|
1826
1648
|
this.#functionBodyDepth++;
|
|
1827
|
-
this.#functionStack.push(node);
|
|
1828
|
-
const context_restore = {
|
|
1829
|
-
parentContext: [...this.context],
|
|
1830
|
-
canRestore:
|
|
1831
|
-
this.#isInsideComponent() &&
|
|
1832
|
-
this.context.some((context) => context === tstc.tc_oTag || context === tstc.tc_cTag),
|
|
1833
|
-
restore: false,
|
|
1834
|
-
};
|
|
1835
|
-
this.#functionBodyContextRestoreStack.push(context_restore);
|
|
1836
|
-
// Inside a component, nested JS function bodies should parse like
|
|
1837
|
-
// ordinary functions, not component template bodies.
|
|
1838
|
-
if (
|
|
1839
|
-
// Only adjust functions declared while parsing a component body.
|
|
1840
|
-
this.#isInsideComponent() &&
|
|
1841
|
-
// A stale JSX expression context means the surrounding template
|
|
1842
|
-
// tokenizer can still treat `<` as template markup.
|
|
1843
|
-
this.context.some((context) => context === tstc.tc_expr) &&
|
|
1844
|
-
// Keep callback props on their surrounding JSX attribute path until
|
|
1845
|
-
// statement-position TSRX needs to suspend it.
|
|
1846
|
-
!context_restore.canRestore &&
|
|
1847
|
-
// Only reset statement-level function bodies, not expression
|
|
1848
|
-
// contexts that are actively parsing JSX.
|
|
1849
|
-
this.curContext() === b_stat
|
|
1850
|
-
) {
|
|
1851
|
-
this.context = [b_stat];
|
|
1852
|
-
}
|
|
1853
|
-
|
|
1854
1649
|
try {
|
|
1855
1650
|
return super.parseFunctionBody(node, isArrowFunction, isMethod, forInit, ...args);
|
|
1856
1651
|
} finally {
|
|
1857
|
-
if (context_restore.restore) {
|
|
1858
|
-
this.context = context_restore.parentContext.slice(0, -1);
|
|
1859
|
-
this.exprAllowed = false;
|
|
1860
|
-
}
|
|
1861
|
-
this.#functionBodyContextRestoreStack.pop();
|
|
1862
|
-
this.#functionStack.pop();
|
|
1863
1652
|
this.#functionBodyDepth--;
|
|
1864
1653
|
}
|
|
1865
1654
|
}
|
|
1866
1655
|
|
|
1867
|
-
/**
|
|
1868
|
-
* @type {Parse.Parser['checkUnreserved']}
|
|
1869
|
-
*/
|
|
1870
|
-
checkUnreserved(ref) {
|
|
1871
|
-
if (ref.name === 'component') {
|
|
1872
|
-
// Allow 'component' when it's followed by an identifier and '(' or '<' (component method in object literal)
|
|
1873
|
-
// e.g., { component something() { ... } }
|
|
1874
|
-
// Also allow computed names: { component ['name']() { ... } }
|
|
1875
|
-
// Also allow string literal names: { component 'name'() { ... } }
|
|
1876
|
-
const nextChars = this.input.slice(this.pos).match(/^\s*(?:(\w+)\s*[(<]|\[|['"])/);
|
|
1877
|
-
if (!nextChars) {
|
|
1878
|
-
this.raise(
|
|
1879
|
-
ref.start,
|
|
1880
|
-
'"component" is a TSRX keyword and cannot be used as an identifier',
|
|
1881
|
-
);
|
|
1882
|
-
}
|
|
1883
|
-
}
|
|
1884
|
-
return super.checkUnreserved(ref);
|
|
1885
|
-
}
|
|
1886
|
-
|
|
1887
|
-
/** @type {Parse.Parser['shouldParseExportStatement']} */
|
|
1888
|
-
shouldParseExportStatement() {
|
|
1889
|
-
if (super.shouldParseExportStatement()) {
|
|
1890
|
-
return true;
|
|
1891
|
-
}
|
|
1892
|
-
if (this.value === 'component') {
|
|
1893
|
-
return true;
|
|
1894
|
-
}
|
|
1895
|
-
return this.type.keyword === 'var';
|
|
1896
|
-
}
|
|
1897
|
-
|
|
1898
1656
|
/**
|
|
1899
1657
|
* @return {ESTreeJSX.JSXExpressionContainer}
|
|
1900
1658
|
*/
|
|
@@ -1902,59 +1660,8 @@ export function TSRXPlugin(config) {
|
|
|
1902
1660
|
let node = /** @type {ESTreeJSX.JSXExpressionContainer} */ (this.startNode());
|
|
1903
1661
|
this.next();
|
|
1904
1662
|
|
|
1905
|
-
if (this.type === tt.name && this.value === 'ref') {
|
|
1906
|
-
const ref_node = /** @type {AST.RefExpression} */ (this.startNode());
|
|
1907
|
-
this.next();
|
|
1908
|
-
if (this.type === tt.braceR) {
|
|
1909
|
-
this.raise(
|
|
1910
|
-
this.start,
|
|
1911
|
-
'"ref" is a TSRX keyword and must be used in the form {ref item}',
|
|
1912
|
-
);
|
|
1913
|
-
}
|
|
1914
|
-
ref_node.argument = this.parseMaybeAssign();
|
|
1915
|
-
node.expression = /** @type {any} */ (this.finishNode(ref_node, 'RefExpression'));
|
|
1916
|
-
this.expect(tt.braceR);
|
|
1917
|
-
return this.finishNode(node, 'JSXExpressionContainer');
|
|
1918
|
-
}
|
|
1919
|
-
|
|
1920
|
-
if (this.type === tt.name && this.value === 'html') {
|
|
1921
|
-
node.html = true;
|
|
1922
|
-
this.next();
|
|
1923
|
-
if (this.type === tt.braceR) {
|
|
1924
|
-
this.raise(
|
|
1925
|
-
this.start,
|
|
1926
|
-
'"html" is a TSRX keyword and must be used in the form {html some_content}',
|
|
1927
|
-
);
|
|
1928
|
-
}
|
|
1929
|
-
} else if (this.type === tt.name && this.value === 'text') {
|
|
1930
|
-
node.text = true;
|
|
1931
|
-
this.next();
|
|
1932
|
-
if (this.type === tt.braceR) {
|
|
1933
|
-
this.raise(
|
|
1934
|
-
this.start,
|
|
1935
|
-
'"text" is a TSRX keyword and must be used in the form {text some_value}',
|
|
1936
|
-
);
|
|
1937
|
-
}
|
|
1938
|
-
} else if (
|
|
1939
|
-
this.type === tt.name &&
|
|
1940
|
-
this.value === 'style' &&
|
|
1941
|
-
this.lookahead().type === tt.string
|
|
1942
|
-
) {
|
|
1943
|
-
node.style = true;
|
|
1944
|
-
this.next();
|
|
1945
|
-
}
|
|
1946
|
-
|
|
1947
1663
|
node.expression =
|
|
1948
1664
|
this.type === tt.braceR ? this.jsx_parseEmptyExpression() : this.parseExpression();
|
|
1949
|
-
if (
|
|
1950
|
-
node.style &&
|
|
1951
|
-
(node.expression.type !== 'Literal' || typeof node.expression.value !== 'string')
|
|
1952
|
-
) {
|
|
1953
|
-
this.raise(
|
|
1954
|
-
/** @type {number} */ (node.expression.start),
|
|
1955
|
-
'"style" is a TSRX keyword and must be used in the form {style "class_name"}',
|
|
1956
|
-
);
|
|
1957
|
-
}
|
|
1958
1665
|
if (this.#allowExpressionContainerTrailingSemicolon && this.type === tt.semi) {
|
|
1959
1666
|
if (this.#collect) {
|
|
1960
1667
|
this.#report_recoverable_error(
|
|
@@ -2052,39 +1759,7 @@ export function TSRXPlugin(config) {
|
|
|
2052
1759
|
this.unexpected();
|
|
2053
1760
|
}
|
|
2054
1761
|
|
|
2055
|
-
if (this.
|
|
2056
|
-
this.next();
|
|
2057
|
-
if (this.type === tt.braceR) {
|
|
2058
|
-
this.raise(
|
|
2059
|
-
this.start,
|
|
2060
|
-
'"ref" is a Ripple keyword and must be used in the form {ref fn}',
|
|
2061
|
-
);
|
|
2062
|
-
}
|
|
2063
|
-
/** @type {AST.RefAttribute} */ (node).argument = this.parseMaybeAssign();
|
|
2064
|
-
this.expect(tt.braceR);
|
|
2065
|
-
return /** @type {AST.RefAttribute} */ (this.finishNode(node, 'RefAttribute'));
|
|
2066
|
-
} else if (this.type === tt.name && this.value === 'html') {
|
|
2067
|
-
// {html ...}
|
|
2068
|
-
// The support is purely for better error messages to avoid
|
|
2069
|
-
// the parser throw an unexpected token error
|
|
2070
|
-
const id = /** @type {AST.Identifier} */ (this.parseIdentNode());
|
|
2071
|
-
id.tracked = false;
|
|
2072
|
-
this.finishNode(id, 'Identifier');
|
|
2073
|
-
this.next();
|
|
2074
|
-
const value = this.type === tt.braceR ? id : this.parseMaybeAssign();
|
|
2075
|
-
const report_end = this.type === tt.braceR ? this.end : (value.end ?? this.end);
|
|
2076
|
-
this.#report_recoverable_error_range(
|
|
2077
|
-
node.start ?? id.start ?? this.start,
|
|
2078
|
-
report_end,
|
|
2079
|
-
HTML_ATTRIBUTE_VALUE_ERROR,
|
|
2080
|
-
DIAGNOSTIC_CODES.HTML_DIRECTIVE_AS_ATTRIBUTE_VALUE,
|
|
2081
|
-
);
|
|
2082
|
-
/** @type {AST.Attribute} */ (node).name = id;
|
|
2083
|
-
/** @type {AST.Attribute} */ (node).value = value;
|
|
2084
|
-
/** @type {AST.Attribute} */ (node).shorthand = false;
|
|
2085
|
-
this.expect(tt.braceR);
|
|
2086
|
-
return this.finishNode(node, 'Attribute');
|
|
2087
|
-
} else if (this.type === tt.ellipsis) {
|
|
1762
|
+
if (this.type === tt.ellipsis) {
|
|
2088
1763
|
this.expect(tt.ellipsis);
|
|
2089
1764
|
/** @type {AST.SpreadAttribute} */ (node).argument = this.parseMaybeAssign();
|
|
2090
1765
|
this.expect(tt.braceR);
|
|
@@ -2107,17 +1782,23 @@ export function TSRXPlugin(config) {
|
|
|
2107
1782
|
}
|
|
2108
1783
|
}
|
|
2109
1784
|
/** @type {ESTreeJSX.JSXAttribute} */ (node).name = this.jsx_parseNamespacedName();
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
1785
|
+
if (
|
|
1786
|
+
/** @type {ESTreeJSX.JSXAttribute} */ (node).name.type === 'JSXIdentifier' &&
|
|
1787
|
+
/** @type {ESTreeJSX.JSXIdentifier} */ (/** @type {ESTreeJSX.JSXAttribute} */ (node).name)
|
|
1788
|
+
.tracked
|
|
1789
|
+
) {
|
|
2114
1790
|
this.#report_recoverable_error_range(
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
1791
|
+
/** @type {AST.NodeWithLocation} */ (node).start,
|
|
1792
|
+
/** @type {AST.NodeWithLocation} */ (/** @type {ESTreeJSX.JSXAttribute} */ (node).name)
|
|
1793
|
+
.end ??
|
|
1794
|
+
node.end ??
|
|
1795
|
+
node.start,
|
|
1796
|
+
DYNAMIC_ATTRIBUTE_NAME_ERROR,
|
|
2119
1797
|
);
|
|
2120
1798
|
}
|
|
1799
|
+
const value = /** @type {ESTreeJSX.JSXAttribute['value'] | null} */ (
|
|
1800
|
+
this.eat(tt.eq) ? this.jsx_parseAttributeValue() : null
|
|
1801
|
+
);
|
|
2121
1802
|
/** @type {ESTreeJSX.JSXAttribute} */ (node).value = value;
|
|
2122
1803
|
return this.finishNode(node, 'JSXAttribute');
|
|
2123
1804
|
}
|
|
@@ -2213,7 +1894,12 @@ export function TSRXPlugin(config) {
|
|
|
2213
1894
|
jsx_parseAttributeValue() {
|
|
2214
1895
|
switch (this.type) {
|
|
2215
1896
|
case tt.braceL:
|
|
2216
|
-
|
|
1897
|
+
this.#jsxAttributeValueExpressionDepth++;
|
|
1898
|
+
try {
|
|
1899
|
+
return this.jsx_parseExpressionContainer();
|
|
1900
|
+
} finally {
|
|
1901
|
+
this.#jsxAttributeValueExpressionDepth--;
|
|
1902
|
+
}
|
|
2217
1903
|
case tstt.jsxTagStart:
|
|
2218
1904
|
case tt.string:
|
|
2219
1905
|
return this.parseExprAtom();
|
|
@@ -2430,7 +2116,6 @@ export function TSRXPlugin(config) {
|
|
|
2430
2116
|
if (
|
|
2431
2117
|
ch === CharCode.closeBrace &&
|
|
2432
2118
|
(this.#path.length === 0 ||
|
|
2433
|
-
this.#path.at(-1)?.type === 'Component' ||
|
|
2434
2119
|
this.#path.at(-1)?.type === 'Element' ||
|
|
2435
2120
|
this.#path.at(-1)?.type === 'Tsrx')
|
|
2436
2121
|
) {
|
|
@@ -2469,97 +2154,49 @@ export function TSRXPlugin(config) {
|
|
|
2469
2154
|
}
|
|
2470
2155
|
|
|
2471
2156
|
/**
|
|
2472
|
-
* Override jsx_parseElement to
|
|
2473
|
-
*
|
|
2474
|
-
*
|
|
2475
|
-
* for <tsx>...</tsx>. <tsrx>...</tsrx> admits native TSRX
|
|
2476
|
-
* template syntax as an expression value. Other tags must still use
|
|
2477
|
-
* <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.
|
|
2478
2160
|
* @type {Parse.Parser['jsx_parseElement']}
|
|
2479
2161
|
*/
|
|
2480
2162
|
jsx_parseElement() {
|
|
2481
|
-
// Check if the element being parsed IS a <tsx>, <tsrx>, or <tsx:*> tag
|
|
2482
2163
|
// Current token is jsxTagStart, this.end is position after '<'
|
|
2483
2164
|
const tag_name_start = this.end;
|
|
2484
|
-
const is_fragment_tag = this.input.charCodeAt(tag_name_start) === CharCode.greaterThan;
|
|
2485
|
-
const char_after_tsx = this.input.charCodeAt(tag_name_start + 3);
|
|
2486
|
-
const char_after_tsrx = this.input.charCodeAt(tag_name_start + 4);
|
|
2487
|
-
const is_tsx_tag =
|
|
2488
|
-
this.input.startsWith('tsx', tag_name_start) &&
|
|
2489
|
-
(tag_name_start + 3 >= this.input.length ||
|
|
2490
|
-
char_after_tsx === CharCode.greaterThan ||
|
|
2491
|
-
char_after_tsx === CharCode.slash ||
|
|
2492
|
-
char_after_tsx === CharCode.space ||
|
|
2493
|
-
char_after_tsx === CharCode.tab ||
|
|
2494
|
-
char_after_tsx === CharCode.lineFeed ||
|
|
2495
|
-
char_after_tsx === CharCode.carriageReturn ||
|
|
2496
|
-
char_after_tsx === CharCode.colon);
|
|
2497
|
-
const is_tsrx_tag =
|
|
2498
|
-
this.input.startsWith('tsrx', tag_name_start) &&
|
|
2499
|
-
(tag_name_start + 4 >= this.input.length ||
|
|
2500
|
-
char_after_tsrx === CharCode.greaterThan ||
|
|
2501
|
-
char_after_tsrx === CharCode.slash ||
|
|
2502
|
-
char_after_tsrx === CharCode.space ||
|
|
2503
|
-
char_after_tsrx === CharCode.tab ||
|
|
2504
|
-
char_after_tsrx === CharCode.lineFeed ||
|
|
2505
|
-
char_after_tsrx === CharCode.carriageReturn);
|
|
2506
|
-
|
|
2507
2165
|
const current_template_node = this.#path.findLast(
|
|
2508
2166
|
(n) =>
|
|
2509
2167
|
n.type === 'Element' || n.type === 'Tsx' || n.type === 'Tsrx' || n.type === 'TsxCompat',
|
|
2510
2168
|
);
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2169
|
+
const inside_tsx_island =
|
|
2170
|
+
current_template_node?.type === 'Tsx' || current_template_node?.type === 'TsxCompat';
|
|
2171
|
+
if (inside_tsx_island) {
|
|
2172
|
+
if (this.input.charCodeAt(tag_name_start) === CharCode.at) {
|
|
2173
|
+
this.#report_recoverable_error_range(
|
|
2174
|
+
this.start,
|
|
2175
|
+
tag_name_start + 1,
|
|
2176
|
+
DYNAMIC_ELEMENT_IN_TSX_ERROR,
|
|
2177
|
+
);
|
|
2178
|
+
}
|
|
2515
2179
|
// Inside tsx/tsx:*, let acorn-jsx handle regular TSX tags normally.
|
|
2516
|
-
// Nested <tsrx> still needs Ripple's native template parser so it
|
|
2517
|
-
// can lower through the same path as <tsrx> in component bodies.
|
|
2518
2180
|
return super.jsx_parseElement();
|
|
2519
2181
|
}
|
|
2520
2182
|
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
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)),
|
|
2527
2190
|
);
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
);
|
|
2535
|
-
} else if (this.type === tt.braceR && this.curContext() === tstc.tc_expr) {
|
|
2536
|
-
if (this.#tsxIslandExpressionDepth === 0) {
|
|
2537
|
-
// Acorn still owns the surrounding JSX expression container.
|
|
2538
|
-
// Keep a block-expression context for its closing `}` so the
|
|
2539
|
-
// parent TSX tag continues tokenizing as JSX afterward.
|
|
2540
|
-
this.context.push(b_expr);
|
|
2541
|
-
}
|
|
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);
|
|
2542
2197
|
}
|
|
2543
|
-
return parsed;
|
|
2544
2198
|
}
|
|
2545
|
-
|
|
2546
|
-
if (
|
|
2547
|
-
!this.#path.findLast((node) => node.type === 'Component') &&
|
|
2548
|
-
!this.#functionStack.findLast(is_pascal_case_function)
|
|
2549
|
-
) {
|
|
2550
|
-
return super.jsx_parseElement();
|
|
2551
|
-
}
|
|
2552
|
-
|
|
2553
|
-
const code = this.#functionStack.findLast(is_pascal_case_function)
|
|
2554
|
-
? DIAGNOSTIC_CODES.FUNCTION_COMPONENT_SYNTAX
|
|
2555
|
-
: this.#path.findLast((node) => node.type === 'Component') &&
|
|
2556
|
-
this.#functionStack.length === 0 &&
|
|
2557
|
-
previous_word_before(this.input, this.start) === 'return'
|
|
2558
|
-
? DIAGNOSTIC_CODES.JSX_RETURN_IN_COMPONENT
|
|
2559
|
-
: DIAGNOSTIC_CODES.JSX_EXPRESSION_VALUE;
|
|
2560
|
-
|
|
2561
|
-
this.#report_recoverable_error(this.start, JSX_EXPRESSION_VALUE_ERROR, code);
|
|
2562
|
-
return super.jsx_parseElement();
|
|
2199
|
+
return parsed;
|
|
2563
2200
|
}
|
|
2564
2201
|
|
|
2565
2202
|
/**
|
|
@@ -2598,11 +2235,12 @@ export function TSRXPlugin(config) {
|
|
|
2598
2235
|
!is_tsx_compat &&
|
|
2599
2236
|
open.name.type === 'JSXIdentifier' &&
|
|
2600
2237
|
open.name.name === 'tsx';
|
|
2601
|
-
const
|
|
2238
|
+
const is_dynamic_name =
|
|
2602
2239
|
!is_fragment &&
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2240
|
+
((open.name.type === 'JSXIdentifier' && open.name.tracked) ||
|
|
2241
|
+
(open.name.type === 'JSXMemberExpression' &&
|
|
2242
|
+
open.name.object.type === 'JSXIdentifier' &&
|
|
2243
|
+
open.name.object.tracked));
|
|
2606
2244
|
|
|
2607
2245
|
if (is_tsx_compat) {
|
|
2608
2246
|
const namespace_node = /** @type {ESTreeJSX.JSXNamespacedName} */ (open.name);
|
|
@@ -2625,21 +2263,20 @@ export function TSRXPlugin(config) {
|
|
|
2625
2263
|
`TSX elements cannot be self-closing. '<tsx />' must have a closing tag '</tsx>'.`,
|
|
2626
2264
|
);
|
|
2627
2265
|
}
|
|
2628
|
-
} else if (is_tsrx) {
|
|
2629
|
-
/** @type {AST.Tsrx} */ (element).type = 'Tsrx';
|
|
2630
|
-
|
|
2631
|
-
if (open.selfClosing) {
|
|
2632
|
-
this.raise(
|
|
2633
|
-
open.start,
|
|
2634
|
-
`TSRX elements cannot be self-closing. '<tsrx />' must have a closing tag '</tsrx>'.`,
|
|
2635
|
-
);
|
|
2636
|
-
}
|
|
2637
2266
|
} else if (is_fragment) {
|
|
2638
|
-
/** @type {AST.
|
|
2267
|
+
/** @type {AST.Tsrx} */ (element).type = 'Tsrx';
|
|
2639
2268
|
} else {
|
|
2640
2269
|
element.type = 'Element';
|
|
2641
2270
|
}
|
|
2642
2271
|
|
|
2272
|
+
if (is_tsx && is_dynamic_name) {
|
|
2273
|
+
this.#report_recoverable_error_range(
|
|
2274
|
+
open.name.start ?? open.start,
|
|
2275
|
+
open.name.end ?? open.end,
|
|
2276
|
+
DYNAMIC_ELEMENT_IN_TSX_ERROR,
|
|
2277
|
+
);
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2643
2280
|
for (const attr of open.attributes) {
|
|
2644
2281
|
if (attr.type === 'JSXAttribute') {
|
|
2645
2282
|
/** @type {AST.Attribute} */ (/** @type {unknown} */ (attr)).type = 'Attribute';
|
|
@@ -2650,14 +2287,6 @@ export function TSRXPlugin(config) {
|
|
|
2650
2287
|
if (attr.value !== null) {
|
|
2651
2288
|
if (attr.value.type === 'JSXExpressionContainer') {
|
|
2652
2289
|
const expression = attr.value.expression;
|
|
2653
|
-
if (attr.value.style) {
|
|
2654
|
-
/** @type {AST.Style} */ (/** @type {unknown} */ (attr.value)).type = 'Style';
|
|
2655
|
-
/** @type {AST.Style} */ (/** @type {unknown} */ (attr.value)).value =
|
|
2656
|
-
/** @type {AST.Literal} */ (expression);
|
|
2657
|
-
delete (/** @type {any} */ (attr.value).expression);
|
|
2658
|
-
delete (/** @type {any} */ (attr.value).style);
|
|
2659
|
-
continue;
|
|
2660
|
-
}
|
|
2661
2290
|
if (expression.type === 'Literal') {
|
|
2662
2291
|
expression.was_expression = true;
|
|
2663
2292
|
}
|
|
@@ -2669,7 +2298,7 @@ export function TSRXPlugin(config) {
|
|
|
2669
2298
|
}
|
|
2670
2299
|
}
|
|
2671
2300
|
|
|
2672
|
-
if (!is_tsx_compat && !is_tsx && !
|
|
2301
|
+
if (!is_tsx_compat && !is_tsx && !is_fragment) {
|
|
2673
2302
|
/** @type {AST.Element} */ (element).id = /** @type {AST.Identifier} */ (
|
|
2674
2303
|
convert_from_jsx(/** @type {ESTreeJSX.JSXIdentifier} */ (open.name))
|
|
2675
2304
|
);
|
|
@@ -2697,27 +2326,26 @@ export function TSRXPlugin(config) {
|
|
|
2697
2326
|
} else if (is_fragment) {
|
|
2698
2327
|
this.#parseNativeTemplateBody(element, /** @type {AST.Element} */ (element).children, {
|
|
2699
2328
|
enterScope: true,
|
|
2329
|
+
resetFunctionBodyDepth: true,
|
|
2700
2330
|
});
|
|
2701
2331
|
|
|
2702
|
-
|
|
2703
|
-
this.#path.pop();
|
|
2332
|
+
this.#path.pop();
|
|
2704
2333
|
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2334
|
+
if (!element.unclosed) {
|
|
2335
|
+
const raise_error = () => {
|
|
2336
|
+
this.raise(this.start, `Expected closing tag '</>'`);
|
|
2337
|
+
};
|
|
2709
2338
|
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
}
|
|
2718
|
-
this.#popTsxTokenContextBeforeTemplateExpressionChild();
|
|
2719
|
-
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();
|
|
2720
2346
|
}
|
|
2347
|
+
this.#popTsxTokenContextBeforeTemplateExpressionChild();
|
|
2348
|
+
this.next();
|
|
2721
2349
|
}
|
|
2722
2350
|
} else {
|
|
2723
2351
|
if (/** @type {ESTreeJSX.JSXIdentifier} */ (open.name).name === 'script') {
|
|
@@ -2790,7 +2418,7 @@ export function TSRXPlugin(config) {
|
|
|
2790
2418
|
// No closing tag
|
|
2791
2419
|
this.#report_broken_markup_error(
|
|
2792
2420
|
open.end,
|
|
2793
|
-
"Unclosed tag '<script>'. Expected '</script>' before end of
|
|
2421
|
+
"Unclosed tag '<script>'. Expected '</script>' before end of template.",
|
|
2794
2422
|
);
|
|
2795
2423
|
/** @type {AST.Element} */ (element).unclosed = true;
|
|
2796
2424
|
this.#path.pop();
|
|
@@ -2803,16 +2431,8 @@ export function TSRXPlugin(config) {
|
|
|
2803
2431
|
const end = input.indexOf('</style>');
|
|
2804
2432
|
const content = end === -1 ? input : input.slice(0, end);
|
|
2805
2433
|
|
|
2806
|
-
const component = /** @type {AST.Component} */ (
|
|
2807
|
-
this.#path.findLast((n) => n.type === 'Component')
|
|
2808
|
-
);
|
|
2809
2434
|
const parsed_css = parse_style(content, { loose: this.#loose });
|
|
2810
|
-
|
|
2811
2435
|
if (!inside_head) {
|
|
2812
|
-
if (component.css !== null) {
|
|
2813
|
-
throw new Error('Components can only have one style tag');
|
|
2814
|
-
}
|
|
2815
|
-
component.css = parsed_css;
|
|
2816
2436
|
/** @type {AST.Element} */ (element).metadata.styleScopeHash = parsed_css.hash;
|
|
2817
2437
|
}
|
|
2818
2438
|
|
|
@@ -2850,7 +2470,7 @@ export function TSRXPlugin(config) {
|
|
|
2850
2470
|
} else {
|
|
2851
2471
|
this.#report_broken_markup_error(
|
|
2852
2472
|
open.end,
|
|
2853
|
-
"Unclosed tag '<style>'. Expected '</style>' before end of
|
|
2473
|
+
"Unclosed tag '<style>'. Expected '</style>' before end of template.",
|
|
2854
2474
|
);
|
|
2855
2475
|
/** @type {AST.Element} */ (element).unclosed = true;
|
|
2856
2476
|
this.#path.pop();
|
|
@@ -2874,7 +2494,11 @@ export function TSRXPlugin(config) {
|
|
|
2874
2494
|
} else {
|
|
2875
2495
|
this.#parseNativeTemplateBody(element, /** @type {AST.Element} */ (element).children, {
|
|
2876
2496
|
enterScope: true,
|
|
2497
|
+
resetFunctionBodyDepth: true,
|
|
2877
2498
|
});
|
|
2499
|
+
if (/** @type {AST.Tsx} */ (element).type === 'Tsx') {
|
|
2500
|
+
this.#reportDynamicJsxElementsInTsx(/** @type {AST.Element} */ (element).children);
|
|
2501
|
+
}
|
|
2878
2502
|
|
|
2879
2503
|
if (/** @type {AST.Tsx} */ (element).type === 'Tsx') {
|
|
2880
2504
|
this.#path.pop();
|
|
@@ -2939,9 +2563,10 @@ export function TSRXPlugin(config) {
|
|
|
2939
2563
|
/** @type {AST.Tsrx} */ (element).type === 'Tsrx' &&
|
|
2940
2564
|
this.#path[this.#path.length - 1] === element
|
|
2941
2565
|
) {
|
|
2566
|
+
const displayTag = element.openingElement.name ? 'tsrx' : '';
|
|
2942
2567
|
this.#report_broken_markup_error(
|
|
2943
2568
|
this.start,
|
|
2944
|
-
|
|
2569
|
+
`Unclosed tag '<${displayTag}>'. Expected '</${displayTag}>' before end of template.`,
|
|
2945
2570
|
);
|
|
2946
2571
|
element.unclosed = true;
|
|
2947
2572
|
/** @type {AST.SourceLocation} */ (element.loc).end = {
|
|
@@ -2957,7 +2582,7 @@ export function TSRXPlugin(config) {
|
|
|
2957
2582
|
const tagName = this.getElementName(element.id);
|
|
2958
2583
|
this.#report_broken_markup_error(
|
|
2959
2584
|
this.start,
|
|
2960
|
-
`Unclosed tag '<${tagName}>'. Expected '</${tagName}>' before end of
|
|
2585
|
+
`Unclosed tag '<${tagName}>'. Expected '</${tagName}>' before end of template.`,
|
|
2961
2586
|
);
|
|
2962
2587
|
element.unclosed = true;
|
|
2963
2588
|
/** @type {AST.SourceLocation} */ (element.loc).end = {
|
|
@@ -2978,13 +2603,7 @@ export function TSRXPlugin(config) {
|
|
|
2978
2603
|
}
|
|
2979
2604
|
}
|
|
2980
2605
|
|
|
2981
|
-
if (
|
|
2982
|
-
element.closingElement &&
|
|
2983
|
-
!is_tsx_compat &&
|
|
2984
|
-
!is_tsx &&
|
|
2985
|
-
!is_tsrx &&
|
|
2986
|
-
element.closingElement.name
|
|
2987
|
-
) {
|
|
2606
|
+
if (element.closingElement && !is_tsx_compat && !is_tsx && element.closingElement.name) {
|
|
2988
2607
|
/** @type {unknown} */ (element.closingElement.name) = convert_from_jsx(
|
|
2989
2608
|
element.closingElement.name,
|
|
2990
2609
|
);
|
|
@@ -3021,10 +2640,10 @@ export function TSRXPlugin(config) {
|
|
|
3021
2640
|
|
|
3022
2641
|
if (!inside_func) {
|
|
3023
2642
|
if (this.type.label === 'continue') {
|
|
3024
|
-
throw new Error('`continue` statements are not allowed in
|
|
2643
|
+
throw new Error('`continue` statements are not allowed in native templates');
|
|
3025
2644
|
}
|
|
3026
2645
|
if (this.type.label === 'break') {
|
|
3027
|
-
throw new Error('`break` statements are not allowed in
|
|
2646
|
+
throw new Error('`break` statements are not allowed in native templates');
|
|
3028
2647
|
}
|
|
3029
2648
|
}
|
|
3030
2649
|
|
|
@@ -3035,6 +2654,16 @@ export function TSRXPlugin(config) {
|
|
|
3035
2654
|
);
|
|
3036
2655
|
return;
|
|
3037
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
|
+
}
|
|
3038
2667
|
if (this.type === tt.braceL) {
|
|
3039
2668
|
body.push(this.#parseNativeTemplateExpressionContainer());
|
|
3040
2669
|
} else if (
|
|
@@ -3043,7 +2672,7 @@ export function TSRXPlugin(config) {
|
|
|
3043
2672
|
) {
|
|
3044
2673
|
body.push(this.parseDoubleQuotedTextChild());
|
|
3045
2674
|
} else if (this.type === tt.braceR) {
|
|
3046
|
-
// Leaving a
|
|
2675
|
+
// Leaving a native template body. We may still be in TSX/JSX tokenization
|
|
3047
2676
|
// context (e.g. after parsing markup), but the closing `}` is a JS token.
|
|
3048
2677
|
// If we don't reset this here, the following `next()` can read EOF using
|
|
3049
2678
|
// `jsx_readToken()` and throw "Unterminated JSX contents".
|
|
@@ -3061,8 +2690,8 @@ export function TSRXPlugin(config) {
|
|
|
3061
2690
|
if (this.type === tstt.jsxTagStart) {
|
|
3062
2691
|
this.next();
|
|
3063
2692
|
} else {
|
|
3064
|
-
// A control-flow block inside
|
|
3065
|
-
// 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
|
|
3066
2695
|
// `<` token. Re-enter JSX closing-tag parsing manually.
|
|
3067
2696
|
this.pos = startPos + 1;
|
|
3068
2697
|
this.type = tstt.jsxTagStart;
|
|
@@ -3111,7 +2740,7 @@ export function TSRXPlugin(config) {
|
|
|
3111
2740
|
? closingElement.name.namespace.name + ':' + closingElement.name.name.name
|
|
3112
2741
|
: this.getElementName(closingElement.name);
|
|
3113
2742
|
} else if (currentElement.type === 'Tsrx') {
|
|
3114
|
-
openingTagName = '
|
|
2743
|
+
openingTagName = '';
|
|
3115
2744
|
closingTagName =
|
|
3116
2745
|
closingElement.name?.type === 'JSXNamespacedName'
|
|
3117
2746
|
? closingElement.name.namespace.name + ':' + closingElement.name.name.name
|
|
@@ -3137,7 +2766,7 @@ export function TSRXPlugin(config) {
|
|
|
3137
2766
|
while (this.#path.length > 0) {
|
|
3138
2767
|
const elem = this.#path[this.#path.length - 1];
|
|
3139
2768
|
|
|
3140
|
-
// Stop at non-
|
|
2769
|
+
// Stop at non-template boundaries.
|
|
3141
2770
|
if (
|
|
3142
2771
|
elem.type !== 'Element' &&
|
|
3143
2772
|
elem.type !== 'Tsx' &&
|
|
@@ -3155,7 +2784,7 @@ export function TSRXPlugin(config) {
|
|
|
3155
2784
|
? 'tsx'
|
|
3156
2785
|
: null
|
|
3157
2786
|
: elem.type === 'Tsrx'
|
|
3158
|
-
? '
|
|
2787
|
+
? ''
|
|
3159
2788
|
: elem.id
|
|
3160
2789
|
? this.getElementName(elem.id)
|
|
3161
2790
|
: null;
|
|
@@ -3183,7 +2812,7 @@ export function TSRXPlugin(config) {
|
|
|
3183
2812
|
) {
|
|
3184
2813
|
const elementToCloseName =
|
|
3185
2814
|
elementToClose.type === 'Tsrx'
|
|
3186
|
-
? '
|
|
2815
|
+
? ''
|
|
3187
2816
|
: /** @type {AST.Element} */ (elementToClose).id
|
|
3188
2817
|
? this.getElementName(/** @type {AST.Element} */ (elementToClose).id)
|
|
3189
2818
|
: null;
|
|
@@ -3204,6 +2833,7 @@ export function TSRXPlugin(config) {
|
|
|
3204
2833
|
} else {
|
|
3205
2834
|
skipWhitespace(this);
|
|
3206
2835
|
const node = this.parseStatement(null);
|
|
2836
|
+
this.#report_invalid_template_return_statements(node);
|
|
3207
2837
|
body.push(node);
|
|
3208
2838
|
|
|
3209
2839
|
// Ensure we're not in JSX context before recursing
|
|
@@ -3288,16 +2918,11 @@ export function TSRXPlugin(config) {
|
|
|
3288
2918
|
this.type === tt.braceL &&
|
|
3289
2919
|
this.context.some((c) => c === tstc.tc_expr)
|
|
3290
2920
|
) {
|
|
3291
|
-
return /** @type {ESTreeJSX.JSXEmptyExpression | AST.TSRXExpression | AST.
|
|
2921
|
+
return /** @type {ESTreeJSX.JSXEmptyExpression | AST.TSRXExpression | AST.TextNode | ESTreeJSX.JSXExpressionContainer} */ (
|
|
3292
2922
|
/** @type {unknown} */ (this.#parseNativeTemplateExpressionContainer())
|
|
3293
2923
|
);
|
|
3294
2924
|
}
|
|
3295
2925
|
|
|
3296
|
-
if (this.value === 'component') {
|
|
3297
|
-
this.awaitPos = 0;
|
|
3298
|
-
return this.parseComponent({ requireName: true, declareName: true });
|
|
3299
|
-
}
|
|
3300
|
-
|
|
3301
2926
|
if (this.type === tstt.jsxTagStart) {
|
|
3302
2927
|
this.next();
|
|
3303
2928
|
if (this.value === '/') {
|
|
@@ -3317,18 +2942,6 @@ export function TSRXPlugin(config) {
|
|
|
3317
2942
|
this.context.pop();
|
|
3318
2943
|
}
|
|
3319
2944
|
}
|
|
3320
|
-
const context_restore = this.#functionBodyContextRestoreStack.at(-1);
|
|
3321
|
-
if (
|
|
3322
|
-
this.#functionBodyDepth > 0 &&
|
|
3323
|
-
node.type === 'Tsrx' &&
|
|
3324
|
-
context_restore?.canRestore &&
|
|
3325
|
-
this.type !== tt.braceR &&
|
|
3326
|
-
this.type !== tt.comma
|
|
3327
|
-
) {
|
|
3328
|
-
context_restore.restore = true;
|
|
3329
|
-
this.context = [b_stat];
|
|
3330
|
-
this.exprAllowed = true;
|
|
3331
|
-
}
|
|
3332
2945
|
return node;
|
|
3333
2946
|
}
|
|
3334
2947
|
|
|
@@ -3336,7 +2949,7 @@ export function TSRXPlugin(config) {
|
|
|
3336
2949
|
this.#functionBodyDepth === 0 &&
|
|
3337
2950
|
this.type === tt.string &&
|
|
3338
2951
|
this.input.charCodeAt(this.start) === CharCode.doubleQuote &&
|
|
3339
|
-
(this.#path.at(-1)?.type === '
|
|
2952
|
+
(this.#path.at(-1)?.type === 'Element' || this.#path.at(-1)?.type === 'Tsrx')
|
|
3340
2953
|
) {
|
|
3341
2954
|
this.pos = this.start;
|
|
3342
2955
|
this.#readDoubleQuotedTextChildToken();
|
|
@@ -3386,11 +2999,11 @@ export function TSRXPlugin(config) {
|
|
|
3386
2999
|
const parent = this.#path.at(-1);
|
|
3387
3000
|
|
|
3388
3001
|
// Inside a JS function body, parse `{...}` as a regular block statement,
|
|
3389
|
-
// even if the nearest `#path` entry is a
|
|
3002
|
+
// even if the nearest `#path` entry is a native template — we're in a
|
|
3390
3003
|
// nested function callable, not in a template.
|
|
3391
3004
|
if (
|
|
3392
3005
|
this.#functionBodyDepth === 0 &&
|
|
3393
|
-
(parent?.type === '
|
|
3006
|
+
(parent?.type === 'Element' || parent?.type === 'Tsrx')
|
|
3394
3007
|
) {
|
|
3395
3008
|
if (createNewLexicalScope === void 0) createNewLexicalScope = true;
|
|
3396
3009
|
if (node === void 0) node = /** @type {AST.BlockStatement} */ (this.startNode());
|