@tsrx/core 0.1.19 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/analyze/prune.js +119 -61
- package/src/analyze/validation.js +122 -5
- package/src/index.js +11 -3
- package/src/parse/index.js +157 -99
- package/src/plugin.js +2417 -956
- package/src/runtime/iterable.js +15 -13
- package/src/scope.js +25 -10
- package/src/source-map-utils.js +1 -1
- package/src/transform/await.js +5 -1
- package/src/transform/jsx/ast-builders.js +46 -62
- package/src/transform/jsx/helpers.js +8 -5
- package/src/transform/jsx/index.js +756 -358
- package/src/transform/jsx-interleave.js +1 -2
- package/src/transform/scoping.js +26 -63
- package/src/transform/segments.js +53 -5
- package/src/transform/style-ref.js +3 -11
- package/src/utils/builders.js +2 -3
- package/types/index.d.ts +181 -125
- package/types/jsx-platform.d.ts +6 -11
- package/types/parse.d.ts +36 -10
- package/types/runtime/ref.d.ts +1 -0
package/src/plugin.js
CHANGED
|
@@ -5,20 +5,17 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import * as acorn from 'acorn';
|
|
8
|
-
import { parse_style } from './parse/style.js';
|
|
9
8
|
import {
|
|
10
|
-
convert_from_jsx,
|
|
11
9
|
skipWhitespace,
|
|
12
10
|
isWhitespaceTextNode,
|
|
13
11
|
BINDING_TYPES,
|
|
14
12
|
DestructuringErrors,
|
|
15
13
|
} from './parse/index.js';
|
|
14
|
+
import { parse_style } from './parse/style.js';
|
|
16
15
|
import { regex_newline_characters } from './utils/patterns.js';
|
|
17
16
|
import { error } from './errors.js';
|
|
18
17
|
import { DIAGNOSTIC_CODES } from './diagnostics.js';
|
|
19
18
|
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
19
|
const DYNAMIC_ATTRIBUTE_NAME_ERROR =
|
|
23
20
|
'Dynamic component / element syntax (`@`) is only supported on native TSRX element names, not attribute names.';
|
|
24
21
|
|
|
@@ -28,12 +25,14 @@ const CharCode = Object.freeze({
|
|
|
28
25
|
carriageReturn: 13,
|
|
29
26
|
space: 32,
|
|
30
27
|
doubleQuote: 34,
|
|
28
|
+
numberSign: 35,
|
|
31
29
|
dollar: 36,
|
|
32
30
|
ampersand: 38,
|
|
33
31
|
singleQuote: 39,
|
|
34
32
|
openParen: 40,
|
|
35
33
|
closeParen: 41,
|
|
36
34
|
asterisk: 42,
|
|
35
|
+
dash: 45,
|
|
37
36
|
slash: 47,
|
|
38
37
|
colon: 58,
|
|
39
38
|
semicolon: 59,
|
|
@@ -46,6 +45,7 @@ const CharCode = Object.freeze({
|
|
|
46
45
|
uppercaseA: 65,
|
|
47
46
|
uppercaseZ: 90,
|
|
48
47
|
openBracket: 91,
|
|
48
|
+
closeBracket: 93,
|
|
49
49
|
backslash: 92,
|
|
50
50
|
underscore: 95,
|
|
51
51
|
backtick: 96,
|
|
@@ -55,6 +55,27 @@ const CharCode = Object.freeze({
|
|
|
55
55
|
closeBrace: 125,
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Keywords after which a `/` begins a regex literal rather than division, used
|
|
60
|
+
* by the look-ahead scanners to track expression position in script content.
|
|
61
|
+
*/
|
|
62
|
+
const REGEX_PRECEDING_KEYWORDS = new Set([
|
|
63
|
+
'return',
|
|
64
|
+
'typeof',
|
|
65
|
+
'instanceof',
|
|
66
|
+
'in',
|
|
67
|
+
'of',
|
|
68
|
+
'new',
|
|
69
|
+
'delete',
|
|
70
|
+
'void',
|
|
71
|
+
'do',
|
|
72
|
+
'else',
|
|
73
|
+
'yield',
|
|
74
|
+
'await',
|
|
75
|
+
'case',
|
|
76
|
+
'throw',
|
|
77
|
+
]);
|
|
78
|
+
|
|
58
79
|
/** @type {WeakMap<Record<string, boolean>, Map<string, number>>} */
|
|
59
80
|
const argument_clash_first_positions = new WeakMap();
|
|
60
81
|
/** @type {WeakMap<Record<string, boolean>, Set<string>>} */
|
|
@@ -224,8 +245,6 @@ export function TSRXPlugin(config) {
|
|
|
224
245
|
class TSRXParser extends Parser {
|
|
225
246
|
/** @type {AST.Node[]} */
|
|
226
247
|
#path = [];
|
|
227
|
-
#allowTagStartAfterDoubleQuotedText = false;
|
|
228
|
-
#allowDoubleQuotedTextChildAfterBrace = false;
|
|
229
248
|
#commentContextId = 0;
|
|
230
249
|
#collect = false;
|
|
231
250
|
#loose = false;
|
|
@@ -235,8 +254,24 @@ export function TSRXPlugin(config) {
|
|
|
235
254
|
#filename = null;
|
|
236
255
|
#functionBodyDepth = 0;
|
|
237
256
|
#allowExpressionContainerTrailingSemicolon = false;
|
|
238
|
-
#tsxIslandExpressionDepth = 0;
|
|
239
257
|
#jsxAttributeValueExpressionDepth = 0;
|
|
258
|
+
#jsxExpressionContainerDepth = 0;
|
|
259
|
+
#consumeContainerBraceAfterScope = false;
|
|
260
|
+
#scriptJSXElementDepth = 0;
|
|
261
|
+
#forceScriptJSXElementDepth = 0;
|
|
262
|
+
#suppressTemplateRawTextToken = false;
|
|
263
|
+
#templateScriptParsingDepth = 0;
|
|
264
|
+
#controlFlowBlockAllowsNativeReturn = false;
|
|
265
|
+
#parsingJSXSwitchCaseScriptStatementDepth = 0;
|
|
266
|
+
#templateControlFlowBlockDepth = 0;
|
|
267
|
+
#templateControlFlowTryDepth = 0;
|
|
268
|
+
/** @type {Parse.Parser['context']} */
|
|
269
|
+
context = [b_stat];
|
|
270
|
+
/** @type {AST.Node | null} */
|
|
271
|
+
#openingNativeTemplateNode = null;
|
|
272
|
+
#closingNativeTemplateNode = false;
|
|
273
|
+
#readingJSXControlFlowDirectiveKeyword = false;
|
|
274
|
+
#readingJSXControlFlowHeader = false;
|
|
240
275
|
|
|
241
276
|
/**
|
|
242
277
|
* @type {Parse.Parser['finishNode']}
|
|
@@ -260,6 +295,7 @@ export function TSRXPlugin(config) {
|
|
|
260
295
|
*/
|
|
261
296
|
constructor(options, input) {
|
|
262
297
|
super(options, input);
|
|
298
|
+
this.context ??= [b_stat];
|
|
263
299
|
const tsrx_options = options?.tsrxOptions ?? options?.rippleOptions;
|
|
264
300
|
this.#collect = tsrx_options?.collect === true || tsrx_options?.loose === true;
|
|
265
301
|
this.#loose = tsrx_options?.loose === true;
|
|
@@ -267,6 +303,7 @@ export function TSRXPlugin(config) {
|
|
|
267
303
|
this.#filename = tsrx_options?.filename || null;
|
|
268
304
|
}
|
|
269
305
|
|
|
306
|
+
/** @this {Parse.Parser} */
|
|
270
307
|
#resetTokenStartToCurrentPosition() {
|
|
271
308
|
if (this.start !== this.pos) {
|
|
272
309
|
this.start = this.pos;
|
|
@@ -274,23 +311,6 @@ export function TSRXPlugin(config) {
|
|
|
274
311
|
}
|
|
275
312
|
}
|
|
276
313
|
|
|
277
|
-
#previousNonWhitespaceChar() {
|
|
278
|
-
let index = this.pos - 1;
|
|
279
|
-
while (index >= 0) {
|
|
280
|
-
const ch = this.input.charCodeAt(index);
|
|
281
|
-
if (
|
|
282
|
-
ch !== CharCode.space &&
|
|
283
|
-
ch !== CharCode.tab &&
|
|
284
|
-
ch !== CharCode.lineFeed &&
|
|
285
|
-
ch !== CharCode.carriageReturn
|
|
286
|
-
) {
|
|
287
|
-
return ch;
|
|
288
|
-
}
|
|
289
|
-
index--;
|
|
290
|
-
}
|
|
291
|
-
return null;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
314
|
/**
|
|
295
315
|
* Native TSRX template bodies share one grammar across elements and fragments.
|
|
296
316
|
* This helper keeps the parser-state setup in one place while callers keep
|
|
@@ -336,238 +356,1592 @@ export function TSRXPlugin(config) {
|
|
|
336
356
|
}
|
|
337
357
|
}
|
|
338
358
|
|
|
359
|
+
/**
|
|
360
|
+
* @param {boolean} [createNewLexicalScope]
|
|
361
|
+
* @param {AST.BlockStatement} [node]
|
|
362
|
+
* @param {boolean} [exitStrict]
|
|
363
|
+
* @returns {AST.BlockStatement}
|
|
364
|
+
*/
|
|
365
|
+
#parseTemplateControlFlowBlock(createNewLexicalScope = true, node, exitStrict) {
|
|
366
|
+
node ??= /** @type {AST.BlockStatement} */ (this.startNode());
|
|
367
|
+
// Consume the flag for this block only; nested control-flow blocks
|
|
368
|
+
// parsed inside the body must not inherit it.
|
|
369
|
+
const allows_native_return = this.#controlFlowBlockAllowsNativeReturn;
|
|
370
|
+
this.#controlFlowBlockAllowsNativeReturn = false;
|
|
371
|
+
node.body = [];
|
|
372
|
+
node.metadata = {
|
|
373
|
+
...node.metadata,
|
|
374
|
+
path: [],
|
|
375
|
+
native_tsrx_template_block: true,
|
|
376
|
+
templateMode: 'script',
|
|
377
|
+
allows_native_return,
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
// A directive's `{ }` IS a code block (§2 rule 8): setup statements then
|
|
381
|
+
// at most one render node. Code-only blocks are allowed (§2 rule 6). Hide
|
|
382
|
+
// the enclosing template from `#path` so the body tokenizes as code (not
|
|
383
|
+
// JSX raw text); render nodes re-establish their own path via `parseElement`.
|
|
384
|
+
const enclosing_context = this.context;
|
|
385
|
+
const enclosing_path = this.#path;
|
|
386
|
+
this.context = enclosing_context.filter(
|
|
387
|
+
(context) =>
|
|
388
|
+
context !== tstc.tc_expr && context !== tstc.tc_oTag && context !== tstc.tc_cTag,
|
|
389
|
+
);
|
|
390
|
+
if (this.curContext() !== b_stat) {
|
|
391
|
+
this.context.push(b_stat);
|
|
392
|
+
}
|
|
393
|
+
this.#path = [];
|
|
394
|
+
if (createNewLexicalScope) {
|
|
395
|
+
this.enterScope(0);
|
|
396
|
+
}
|
|
397
|
+
try {
|
|
398
|
+
this.expect(tt.braceL);
|
|
399
|
+
this.#parseCodeBlockBody(node.body);
|
|
400
|
+
} finally {
|
|
401
|
+
if (createNewLexicalScope) {
|
|
402
|
+
this.exitScope();
|
|
403
|
+
}
|
|
404
|
+
this.#path = enclosing_path;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (exitStrict) {
|
|
408
|
+
this.strict = false;
|
|
409
|
+
}
|
|
410
|
+
this.exprAllowed = true;
|
|
411
|
+
this.context = enclosing_context;
|
|
412
|
+
const previous_reading_header = this.#readingJSXControlFlowHeader;
|
|
413
|
+
this.#readingJSXControlFlowHeader = true;
|
|
414
|
+
try {
|
|
415
|
+
this.next();
|
|
416
|
+
} finally {
|
|
417
|
+
this.#readingJSXControlFlowHeader = previous_reading_header;
|
|
418
|
+
}
|
|
419
|
+
return this.finishNode(node, 'BlockStatement');
|
|
420
|
+
}
|
|
421
|
+
|
|
339
422
|
/**
|
|
340
423
|
* @param {AST.Node | undefined} node
|
|
341
424
|
*/
|
|
342
425
|
#isNativeTemplateNode(node) {
|
|
343
426
|
return (
|
|
344
|
-
node?.
|
|
345
|
-
node?.type === '
|
|
346
|
-
node?.type === '
|
|
347
|
-
node?.type === '
|
|
427
|
+
node?.metadata?.native_tsrx_template_block ||
|
|
428
|
+
(node?.type === 'JSXElement' && node.metadata?.native_tsrx) ||
|
|
429
|
+
(node?.type === 'JSXFragment' && node.metadata?.native_tsrx) ||
|
|
430
|
+
(node?.type === 'JSXStyleElement' && node.metadata?.native_tsrx)
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
#currentNativeTemplateNode() {
|
|
435
|
+
return (
|
|
436
|
+
this.#openingNativeTemplateNode ??
|
|
437
|
+
this.#path.findLast((node) => this.#isNativeTemplateNode(node))
|
|
348
438
|
);
|
|
349
439
|
}
|
|
350
440
|
|
|
351
441
|
/**
|
|
352
|
-
* @param {AST.Node
|
|
442
|
+
* @param {AST.Node | undefined} node
|
|
443
|
+
* @param {string} name
|
|
353
444
|
*/
|
|
354
|
-
#
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
-
}
|
|
445
|
+
#isNativeElementNamed(node, name) {
|
|
446
|
+
return (
|
|
447
|
+
(node?.type === 'JSXElement' || node?.type === 'JSXStyleElement') &&
|
|
448
|
+
node.metadata?.native_tsrx &&
|
|
449
|
+
this.getElementName(node.openingElement?.name) === name
|
|
450
|
+
);
|
|
375
451
|
}
|
|
376
452
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
453
|
+
/**
|
|
454
|
+
* @param {any} name
|
|
455
|
+
* @returns {boolean}
|
|
456
|
+
*/
|
|
457
|
+
#isDynamicJSXElementName(name) {
|
|
458
|
+
if (!name || typeof name !== 'object') return false;
|
|
459
|
+
if (name.dynamic === true) return true;
|
|
460
|
+
return name.type === 'JSXMemberExpression' && this.#isDynamicJSXElementName(name.object);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
#isInsideNativeTemplateScriptSection() {
|
|
464
|
+
const node = this.#currentNativeTemplateNode();
|
|
465
|
+
return !!node && node.metadata?.templateMode !== 'template';
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
#isStyleOpeningTagStart() {
|
|
469
|
+
let index = this.start + 1;
|
|
470
|
+
if (this.input.charCodeAt(index) === CharCode.slash) return false;
|
|
471
|
+
if (this.input.slice(index, index + 'style'.length) !== 'style') return false;
|
|
392
472
|
|
|
393
|
-
|
|
394
|
-
|
|
473
|
+
const after = this.input.charCodeAt(index + 'style'.length);
|
|
474
|
+
return (
|
|
475
|
+
after === CharCode.greaterThan ||
|
|
476
|
+
after === CharCode.slash ||
|
|
477
|
+
after === CharCode.space ||
|
|
478
|
+
after === CharCode.tab ||
|
|
479
|
+
after === CharCode.lineFeed ||
|
|
480
|
+
after === CharCode.carriageReturn
|
|
395
481
|
);
|
|
396
482
|
}
|
|
397
483
|
|
|
398
484
|
/**
|
|
399
|
-
* @param {
|
|
400
|
-
* @param {AST.Node[]} body
|
|
485
|
+
* @param {number} index
|
|
401
486
|
*/
|
|
402
|
-
#
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
487
|
+
#isLineStartPosition(index) {
|
|
488
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
489
|
+
const ch = this.input.charCodeAt(i);
|
|
490
|
+
if (ch === CharCode.lineFeed || ch === CharCode.carriageReturn) return true;
|
|
491
|
+
if (ch !== CharCode.space && ch !== CharCode.tab) return false;
|
|
492
|
+
}
|
|
493
|
+
return true;
|
|
494
|
+
}
|
|
409
495
|
|
|
410
|
-
|
|
496
|
+
/**
|
|
497
|
+
* @param {number} index
|
|
498
|
+
*/
|
|
499
|
+
#previousNonSpaceTabIndex(index) {
|
|
500
|
+
let cursor = index - 1;
|
|
501
|
+
while (
|
|
502
|
+
cursor >= 0 &&
|
|
503
|
+
(this.input.charCodeAt(cursor) === CharCode.space ||
|
|
504
|
+
this.input.charCodeAt(cursor) === CharCode.tab)
|
|
505
|
+
) {
|
|
506
|
+
cursor--;
|
|
507
|
+
}
|
|
508
|
+
return cursor;
|
|
509
|
+
}
|
|
411
510
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
};
|
|
423
|
-
island.end = island.openingElement.end;
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
511
|
+
/**
|
|
512
|
+
* @param {number} end_index Inclusive index of the keyword's last character.
|
|
513
|
+
* @param {string} keyword
|
|
514
|
+
*/
|
|
515
|
+
#keywordEndsAt(end_index, keyword) {
|
|
516
|
+
const start = end_index - keyword.length + 1;
|
|
517
|
+
if (start < 0) return false;
|
|
518
|
+
if (this.input.slice(start, end_index + 1) !== keyword) return false;
|
|
519
|
+
return !this.#isIdentifierChar(this.input.charCodeAt(start - 1));
|
|
520
|
+
}
|
|
426
521
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
522
|
+
/**
|
|
523
|
+
* Returns true when a `<` at `index` can start TypeScript type
|
|
524
|
+
* parameters/arguments in expression-like code rather than a JSX tag.
|
|
525
|
+
* Most type argument lists are adjacent to the previous token (`foo<T>`,
|
|
526
|
+
* `build<T>()`, `Map<K, V>`). The whitespace-separated form is valid for
|
|
527
|
+
* anonymous generic function expressions (`function <T>() {}`); generic
|
|
528
|
+
* arrows are handled separately by `looks_like_generic_arrow`.
|
|
529
|
+
*
|
|
530
|
+
* @param {number} index
|
|
531
|
+
*/
|
|
532
|
+
#canStartTypeParameterOrArgumentList(index) {
|
|
533
|
+
const previous = this.#previousNonSpaceTabIndex(index);
|
|
534
|
+
if (previous < 0) return false;
|
|
535
|
+
if (previous === index - 1) {
|
|
536
|
+
return this.#canPrecedeTypeArgumentList(this.input.charCodeAt(previous));
|
|
537
|
+
}
|
|
538
|
+
return this.#keywordEndsAt(previous, 'function');
|
|
539
|
+
}
|
|
431
540
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
541
|
+
#parseTemplateRawText() {
|
|
542
|
+
const start = this.start;
|
|
543
|
+
// The current jsxText token spans `[start, token_end]`. Comments inside
|
|
544
|
+
// that span were already consumed and recorded by the tokenizer
|
|
545
|
+
// (`jsx_readToken`); only comments at/after `token_end` (e.g. a body that
|
|
546
|
+
// opens with a comment, where the raw-text token stops before it) still
|
|
547
|
+
// need recording here. Either way we drop `//` lines from the JSXText value
|
|
548
|
+
// and always advance past them so the scan can't re-tokenize the same spot.
|
|
549
|
+
const token_end = this.end;
|
|
550
|
+
let index = start;
|
|
551
|
+
let value = '';
|
|
552
|
+
while (index < this.input.length) {
|
|
553
|
+
if (this.#isTemplateLineCommentStart(index)) {
|
|
554
|
+
const comment_start = index;
|
|
555
|
+
const comment_start_loc = acorn.getLineInfo(this.input, comment_start);
|
|
556
|
+
index += 2;
|
|
557
|
+
while (
|
|
558
|
+
index < this.input.length &&
|
|
559
|
+
this.input.charCodeAt(index) !== CharCode.lineFeed &&
|
|
560
|
+
this.input.charCodeAt(index) !== CharCode.carriageReturn
|
|
561
|
+
) {
|
|
562
|
+
index++;
|
|
440
563
|
}
|
|
441
|
-
this
|
|
442
|
-
|
|
564
|
+
if (this.options.onComment && comment_start >= token_end) {
|
|
565
|
+
const comment_end_loc = acorn.getLineInfo(this.input, index);
|
|
566
|
+
// Pass null metadata so position-based attachment places the comment
|
|
567
|
+
// as a leading comment on the following child (which the JSX printers
|
|
568
|
+
// emit), rather than on the container's `elementLeadingComments`.
|
|
569
|
+
this.options.onComment(
|
|
570
|
+
false,
|
|
571
|
+
this.input.slice(comment_start + 2, index),
|
|
572
|
+
comment_start,
|
|
573
|
+
index,
|
|
574
|
+
new acorn.Position(comment_start_loc.line, comment_start_loc.column),
|
|
575
|
+
new acorn.Position(comment_end_loc.line, comment_end_loc.column),
|
|
576
|
+
/** @type {any} */ (null),
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
continue;
|
|
443
580
|
}
|
|
581
|
+
const ch = this.input.charCodeAt(index);
|
|
582
|
+
if (
|
|
583
|
+
ch === CharCode.lessThan ||
|
|
584
|
+
ch === CharCode.openBrace ||
|
|
585
|
+
ch === CharCode.closeBrace ||
|
|
586
|
+
this.#isJSXControlFlowDirectiveAt(index)
|
|
587
|
+
) {
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
value += this.input[index];
|
|
591
|
+
index++;
|
|
444
592
|
}
|
|
445
|
-
}
|
|
446
593
|
|
|
447
|
-
|
|
448
|
-
this
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
return this.jsx_parseExpressionContainer();
|
|
452
|
-
}
|
|
594
|
+
const endLoc = acorn.getLineInfo(this.input, index);
|
|
595
|
+
const node = /** @type {ESTreeJSX.JSXText} */ (this.startNodeAt(start, this.startLoc));
|
|
596
|
+
node.value = value;
|
|
597
|
+
node.raw = this.input.slice(start, index);
|
|
453
598
|
|
|
454
|
-
|
|
455
|
-
this.
|
|
456
|
-
this.
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
599
|
+
if (node.raw.match(regex_newline_characters)) {
|
|
600
|
+
this.curLine = endLoc.line;
|
|
601
|
+
this.lineStart = index - endLoc.column;
|
|
602
|
+
}
|
|
603
|
+
this.pos = index;
|
|
604
|
+
this.#popTemplateLiteralTokenContext();
|
|
605
|
+
this.next();
|
|
606
|
+
|
|
607
|
+
return this.finishNodeAt(node, 'JSXText', index, endLoc);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* JSX significant-whitespace rule for a template text child. Non-whitespace
|
|
612
|
+
* text is always kept; whitespace-only text is kept only when it is an
|
|
613
|
+
* intentional inline space (no newline) separating two siblings, and dropped
|
|
614
|
+
* when it is layout indentation (contains a newline).
|
|
615
|
+
*
|
|
616
|
+
* @param {ESTreeJSX.JSXText} node
|
|
617
|
+
*/
|
|
618
|
+
#shouldKeepTemplateTextNode(node) {
|
|
619
|
+
if (!isWhitespaceTextNode(node)) {
|
|
620
|
+
return true;
|
|
468
621
|
}
|
|
622
|
+
return node.value !== '' && !regex_newline_characters.test(node.value);
|
|
469
623
|
}
|
|
470
624
|
|
|
471
|
-
#
|
|
472
|
-
|
|
625
|
+
#isSwitchCaseScriptStatementStart() {
|
|
626
|
+
let index = skip_whitespace_from(this.input, this.start);
|
|
627
|
+
|
|
628
|
+
const first = this.input.charCodeAt(index);
|
|
629
|
+
|
|
630
|
+
if (first === CharCode.openBracket || first === CharCode.openBrace) {
|
|
631
|
+
let depth = 0;
|
|
632
|
+
let i = index;
|
|
633
|
+
for (; i < this.input.length; i++) {
|
|
634
|
+
const ch = this.input.charCodeAt(i);
|
|
635
|
+
if (
|
|
636
|
+
ch === CharCode.openBracket ||
|
|
637
|
+
ch === CharCode.openBrace ||
|
|
638
|
+
ch === CharCode.openParen
|
|
639
|
+
) {
|
|
640
|
+
depth++;
|
|
641
|
+
} else if (
|
|
642
|
+
ch === CharCode.closeBracket ||
|
|
643
|
+
ch === CharCode.closeBrace ||
|
|
644
|
+
ch === CharCode.closeParen
|
|
645
|
+
) {
|
|
646
|
+
depth--;
|
|
647
|
+
if (depth === 0) {
|
|
648
|
+
i++;
|
|
649
|
+
break;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (depth !== 0) return false;
|
|
654
|
+
i = skip_whitespace_from(this.input, i);
|
|
655
|
+
if (this.input.charCodeAt(i) !== CharCode.equals) return false;
|
|
656
|
+
const next = this.input.charCodeAt(i + 1);
|
|
657
|
+
return next !== CharCode.equals && next !== CharCode.greaterThan;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
if (
|
|
661
|
+
!this.#isIdentifierChar(first) ||
|
|
662
|
+
(first >= CharCode.digit0 && first <= CharCode.digit9)
|
|
663
|
+
) {
|
|
473
664
|
return false;
|
|
474
665
|
}
|
|
475
666
|
|
|
476
|
-
|
|
667
|
+
const word_start = index;
|
|
668
|
+
index++;
|
|
669
|
+
while (this.#isIdentifierChar(this.input.charCodeAt(index))) {
|
|
670
|
+
index++;
|
|
671
|
+
}
|
|
672
|
+
const word = this.input.slice(word_start, index);
|
|
673
|
+
if (
|
|
674
|
+
word === 'const' ||
|
|
675
|
+
word === 'let' ||
|
|
676
|
+
word === 'var' ||
|
|
677
|
+
word === 'function' ||
|
|
678
|
+
word === 'class' ||
|
|
679
|
+
word === 'if' ||
|
|
680
|
+
word === 'for' ||
|
|
681
|
+
word === 'switch' ||
|
|
682
|
+
word === 'try' ||
|
|
683
|
+
word === 'throw'
|
|
684
|
+
) {
|
|
685
|
+
return true;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
index = skip_whitespace_from(this.input, index);
|
|
689
|
+
if (this.input.charCodeAt(index) !== CharCode.equals) return false;
|
|
690
|
+
const next = this.input.charCodeAt(index + 1);
|
|
691
|
+
return next !== CharCode.equals && next !== CharCode.greaterThan;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
#switchCaseLabelStart(index = this.start) {
|
|
477
695
|
while (index < this.input.length) {
|
|
478
696
|
const ch = this.input.charCodeAt(index);
|
|
479
697
|
if (
|
|
480
|
-
ch
|
|
481
|
-
ch
|
|
482
|
-
ch
|
|
483
|
-
ch
|
|
698
|
+
ch !== CharCode.space &&
|
|
699
|
+
ch !== CharCode.tab &&
|
|
700
|
+
ch !== CharCode.lineFeed &&
|
|
701
|
+
ch !== CharCode.carriageReturn
|
|
484
702
|
) {
|
|
485
|
-
index++;
|
|
486
|
-
} else {
|
|
487
703
|
break;
|
|
488
704
|
}
|
|
705
|
+
index++;
|
|
489
706
|
}
|
|
490
|
-
|
|
491
|
-
if (this.input.charCodeAt(index) !== CharCode.
|
|
492
|
-
|
|
707
|
+
if (!this.#isLineStartPosition(index)) return -1;
|
|
708
|
+
if (this.input.charCodeAt(index) !== CharCode.at) return -1;
|
|
709
|
+
index++;
|
|
710
|
+
if (
|
|
711
|
+
this.input.slice(index, index + 4) === 'case' &&
|
|
712
|
+
!this.#isIdentifierChar(this.input.charCodeAt(index + 4))
|
|
713
|
+
) {
|
|
714
|
+
return index;
|
|
715
|
+
}
|
|
716
|
+
if (
|
|
717
|
+
this.input.slice(index, index + 7) === 'default' &&
|
|
718
|
+
!this.#isIdentifierChar(this.input.charCodeAt(index + 7))
|
|
719
|
+
) {
|
|
720
|
+
return index;
|
|
493
721
|
}
|
|
722
|
+
return -1;
|
|
723
|
+
}
|
|
494
724
|
|
|
495
|
-
|
|
725
|
+
#rewindToSwitchCaseLabel() {
|
|
726
|
+
const start = this.#switchCaseLabelStart();
|
|
727
|
+
if (start === -1) return false;
|
|
728
|
+
while (this.curContext() === tstc.tc_expr) {
|
|
729
|
+
this.context.pop();
|
|
730
|
+
}
|
|
731
|
+
this.pos = start;
|
|
732
|
+
this.start = start;
|
|
733
|
+
this.startLoc = acorn.getLineInfo(this.input, start);
|
|
734
|
+
this.exprAllowed = true;
|
|
735
|
+
this.#suppressTemplateRawTextToken = true;
|
|
736
|
+
this.next();
|
|
737
|
+
return true;
|
|
496
738
|
}
|
|
497
739
|
|
|
498
740
|
/**
|
|
499
741
|
* @param {number} index
|
|
500
742
|
*/
|
|
501
|
-
#
|
|
502
|
-
|
|
743
|
+
#switchCaseBoundaryStart(index) {
|
|
744
|
+
if (!this.#isLineStartPosition(index)) return -1;
|
|
745
|
+
let wordStart = index;
|
|
746
|
+
while (wordStart < this.input.length) {
|
|
747
|
+
const ch = this.input.charCodeAt(wordStart);
|
|
748
|
+
if (ch !== CharCode.space && ch !== CharCode.tab) break;
|
|
749
|
+
wordStart++;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const ch = this.input.charCodeAt(wordStart);
|
|
753
|
+
if (ch === CharCode.closeBrace) return index;
|
|
754
|
+
if (ch === CharCode.at) {
|
|
755
|
+
const keywordStart = wordStart + 1;
|
|
756
|
+
if (
|
|
757
|
+
this.input.slice(keywordStart, keywordStart + 4) === 'case' &&
|
|
758
|
+
!this.#isIdentifierChar(this.input.charCodeAt(keywordStart + 4))
|
|
759
|
+
) {
|
|
760
|
+
return index;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
if (
|
|
764
|
+
this.input.slice(keywordStart, keywordStart + 7) === 'default' &&
|
|
765
|
+
!this.#isIdentifierChar(this.input.charCodeAt(keywordStart + 7))
|
|
766
|
+
) {
|
|
767
|
+
return index;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
for (const keyword of ['break', 'continue', 'return', 'throw']) {
|
|
772
|
+
if (
|
|
773
|
+
this.input.slice(wordStart, wordStart + keyword.length) === keyword &&
|
|
774
|
+
!this.#isIdentifierChar(this.input.charCodeAt(wordStart + keyword.length))
|
|
775
|
+
) {
|
|
776
|
+
return index;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
return -1;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* @param {number} ch
|
|
785
|
+
*/
|
|
786
|
+
#isIdentifierChar(ch) {
|
|
503
787
|
return (
|
|
504
|
-
|
|
505
|
-
(
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
char_after_tsx === CharCode.tab ||
|
|
510
|
-
char_after_tsx === CharCode.lineFeed ||
|
|
511
|
-
char_after_tsx === CharCode.carriageReturn ||
|
|
512
|
-
char_after_tsx === CharCode.colon)
|
|
788
|
+
(ch >= CharCode.uppercaseA && ch <= CharCode.uppercaseZ) ||
|
|
789
|
+
(ch >= CharCode.lowercaseA && ch <= CharCode.lowercaseZ) ||
|
|
790
|
+
(ch >= CharCode.digit0 && ch <= CharCode.digit9) ||
|
|
791
|
+
ch === CharCode.underscore ||
|
|
792
|
+
ch === CharCode.dollar
|
|
513
793
|
);
|
|
514
794
|
}
|
|
515
795
|
|
|
516
796
|
/**
|
|
517
|
-
* @param {
|
|
797
|
+
* @param {number} ch
|
|
518
798
|
*/
|
|
519
|
-
#
|
|
520
|
-
|
|
521
|
-
|
|
799
|
+
#canPrecedeTypeArgumentList(ch) {
|
|
800
|
+
return this.#isIdentifierChar(ch) || ch === CharCode.closeParen;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/** @this {TSRXParser & Parse.Parser} */
|
|
804
|
+
#parseJSXSwitchCaseRawText() {
|
|
805
|
+
const start = this.start;
|
|
806
|
+
let index = start;
|
|
807
|
+
let found_boundary = false;
|
|
808
|
+
while (index < this.input.length) {
|
|
809
|
+
const boundary = this.#switchCaseBoundaryStart(index);
|
|
810
|
+
if (boundary !== -1) {
|
|
811
|
+
index = boundary;
|
|
812
|
+
found_boundary = true;
|
|
813
|
+
break;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const ch = this.input.charCodeAt(index);
|
|
817
|
+
if (
|
|
818
|
+
ch === CharCode.lessThan ||
|
|
819
|
+
ch === CharCode.openBrace ||
|
|
820
|
+
ch === CharCode.closeBrace ||
|
|
821
|
+
ch === CharCode.at
|
|
822
|
+
) {
|
|
823
|
+
break;
|
|
824
|
+
}
|
|
825
|
+
index++;
|
|
522
826
|
}
|
|
523
827
|
|
|
524
|
-
|
|
525
|
-
|
|
828
|
+
const endLoc = acorn.getLineInfo(this.input, index);
|
|
829
|
+
const node = /** @type {ESTreeJSX.JSXText} */ (this.startNodeAt(start, this.startLoc));
|
|
830
|
+
node.value = this.input.slice(start, index);
|
|
831
|
+
node.raw = node.value;
|
|
832
|
+
|
|
833
|
+
if (node.value.match(regex_newline_characters)) {
|
|
834
|
+
this.curLine = endLoc.line;
|
|
835
|
+
this.lineStart = index - endLoc.column;
|
|
836
|
+
}
|
|
837
|
+
this.pos = index;
|
|
838
|
+
if (found_boundary) {
|
|
839
|
+
this.context = this.context.filter(
|
|
840
|
+
(context) =>
|
|
841
|
+
context !== tstc.tc_expr && context !== tstc.tc_oTag && context !== tstc.tc_cTag,
|
|
842
|
+
);
|
|
843
|
+
if (this.curContext() !== b_stat) {
|
|
844
|
+
this.context.push(b_stat);
|
|
845
|
+
}
|
|
846
|
+
this.exprAllowed = true;
|
|
847
|
+
this.#suppressTemplateRawTextToken = true;
|
|
526
848
|
}
|
|
849
|
+
this.next();
|
|
850
|
+
|
|
851
|
+
return this.finishNodeAt(node, 'JSXText', index, endLoc);
|
|
852
|
+
}
|
|
527
853
|
|
|
528
|
-
|
|
854
|
+
#shouldReadTemplateRawTextToken() {
|
|
855
|
+
if (
|
|
856
|
+
this.#closingNativeTemplateNode ||
|
|
857
|
+
this.#readingJSXControlFlowDirectiveKeyword ||
|
|
858
|
+
this.#readingJSXControlFlowHeader ||
|
|
859
|
+
this.#parsingJSXSwitchCaseScriptStatementDepth > 0 ||
|
|
860
|
+
this.#templateScriptParsingDepth > 0 ||
|
|
861
|
+
this.#jsxExpressionContainerDepth > 0
|
|
862
|
+
) {
|
|
863
|
+
return false;
|
|
864
|
+
}
|
|
865
|
+
const current_context_token = this.curContext()?.token;
|
|
866
|
+
if (current_context_token === '<tag' || current_context_token === '</tag') {
|
|
867
|
+
return false;
|
|
868
|
+
}
|
|
869
|
+
if (this.labels.some((label) => label.kind === 'switch')) {
|
|
870
|
+
return false;
|
|
871
|
+
}
|
|
872
|
+
const current_template_node = this.#currentNativeTemplateNode();
|
|
873
|
+
if (!current_template_node || this.#isJSXControlFlowDirectiveAt(this.pos)) {
|
|
874
|
+
return false;
|
|
875
|
+
}
|
|
876
|
+
if (this.#isTemplateLineCommentStart(this.pos)) {
|
|
877
|
+
return false;
|
|
878
|
+
}
|
|
879
|
+
if (this.#switchCaseLabelStart(this.pos) !== -1) {
|
|
880
|
+
return false;
|
|
881
|
+
}
|
|
882
|
+
if (this.input.charCodeAt(this.pos - 1) === CharCode.lessThan) {
|
|
883
|
+
return false;
|
|
884
|
+
}
|
|
885
|
+
if (
|
|
886
|
+
this.input.charCodeAt(this.pos - 1) === CharCode.slash &&
|
|
887
|
+
this.input.charCodeAt(this.pos - 2) === CharCode.lessThan
|
|
888
|
+
) {
|
|
889
|
+
return false;
|
|
890
|
+
}
|
|
891
|
+
if (
|
|
892
|
+
this.input.charCodeAt(this.pos) === CharCode.slash &&
|
|
893
|
+
this.input.charCodeAt(this.pos + 1) === CharCode.greaterThan
|
|
894
|
+
) {
|
|
895
|
+
return false;
|
|
896
|
+
}
|
|
897
|
+
if (
|
|
898
|
+
this.input.charCodeAt(this.pos) === CharCode.greaterThan &&
|
|
899
|
+
this.input.charCodeAt(this.pos - 1) === CharCode.slash &&
|
|
900
|
+
this.input.charCodeAt(this.pos - 2) === CharCode.lessThan
|
|
901
|
+
) {
|
|
529
902
|
return false;
|
|
530
903
|
}
|
|
904
|
+
// Just past a self-closing tag's `/>`: that element has no body, so any
|
|
905
|
+
// following raw text belongs to an enclosing template, not to it. With no
|
|
906
|
+
// enclosing template (e.g. a top-level `return <div />`), the trailing
|
|
907
|
+
// text is plain JS and must not be read as template raw text.
|
|
908
|
+
const opening = this.#openingNativeTemplateNode;
|
|
909
|
+
if (
|
|
910
|
+
opening &&
|
|
911
|
+
current_template_node === opening &&
|
|
912
|
+
/** @type {any} */ (opening).openingElement?.selfClosing &&
|
|
913
|
+
this.input.charCodeAt(this.pos - 1) === CharCode.greaterThan &&
|
|
914
|
+
this.input.charCodeAt(this.pos - 2) === CharCode.slash
|
|
915
|
+
) {
|
|
916
|
+
const enclosing = this.#path.findLast(
|
|
917
|
+
(node) => node !== opening && this.#isNativeTemplateNode(node),
|
|
918
|
+
);
|
|
919
|
+
if (!enclosing) {
|
|
920
|
+
return false;
|
|
921
|
+
}
|
|
922
|
+
return true;
|
|
923
|
+
}
|
|
924
|
+
return true;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
#readTemplateRawTextToken() {
|
|
928
|
+
const start = this.pos;
|
|
929
|
+
const index = this.#templateRawTextEnd(start);
|
|
930
|
+
|
|
931
|
+
const endLoc = acorn.getLineInfo(this.input, index);
|
|
932
|
+
const value = this.input.slice(start, index);
|
|
933
|
+
if (value.match(regex_newline_characters)) {
|
|
934
|
+
this.curLine = endLoc.line;
|
|
935
|
+
this.lineStart = index - endLoc.column;
|
|
936
|
+
}
|
|
937
|
+
this.pos = index;
|
|
938
|
+
return this.finishToken(tstt.jsxText, value);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* @param {number} index
|
|
943
|
+
*/
|
|
944
|
+
#isTemplateLineCommentStart(index) {
|
|
945
|
+
return (
|
|
946
|
+
this.input.charCodeAt(index) === CharCode.slash &&
|
|
947
|
+
this.input.charCodeAt(index + 1) === CharCode.slash &&
|
|
948
|
+
this.#isLineStartPosition(index)
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* @param {number} start
|
|
954
|
+
*/
|
|
955
|
+
#templateRawTextEnd(start) {
|
|
956
|
+
let index = start;
|
|
957
|
+
while (index < this.input.length) {
|
|
958
|
+
const ch = this.input.charCodeAt(index);
|
|
959
|
+
if (
|
|
960
|
+
ch === CharCode.lessThan ||
|
|
961
|
+
ch === CharCode.openBrace ||
|
|
962
|
+
ch === CharCode.closeBrace ||
|
|
963
|
+
this.#isJSXControlFlowDirectiveAt(index) ||
|
|
964
|
+
this.#isTemplateLineCommentStart(index)
|
|
965
|
+
) {
|
|
966
|
+
break;
|
|
967
|
+
}
|
|
968
|
+
index++;
|
|
969
|
+
}
|
|
970
|
+
return index;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* @param {number} index
|
|
975
|
+
*/
|
|
976
|
+
#isJSXControlFlowDirectiveAt(index) {
|
|
977
|
+
if (this.input.charCodeAt(index) !== CharCode.at) return false;
|
|
978
|
+
|
|
979
|
+
let cursor = index + 1;
|
|
980
|
+
if (!this.#isIdentifierChar(this.input.charCodeAt(cursor))) return false;
|
|
981
|
+
|
|
982
|
+
const word_start = cursor;
|
|
983
|
+
cursor++;
|
|
984
|
+
while (this.#isIdentifierChar(this.input.charCodeAt(cursor))) {
|
|
985
|
+
cursor++;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
const word = this.input.slice(word_start, cursor);
|
|
989
|
+
const next_non_whitespace = skip_whitespace_from(this.input, cursor);
|
|
990
|
+
const next = this.input.charCodeAt(next_non_whitespace);
|
|
991
|
+
if (this.#isIdentifierChar(this.input.charCodeAt(cursor))) {
|
|
992
|
+
return false;
|
|
993
|
+
}
|
|
994
|
+
if (word === 'try') {
|
|
995
|
+
return next === CharCode.openBrace;
|
|
996
|
+
}
|
|
997
|
+
if (word === 'for') {
|
|
998
|
+
if (next === CharCode.openParen) return true;
|
|
999
|
+
if (
|
|
1000
|
+
this.input.slice(next_non_whitespace, next_non_whitespace + 5) === 'await' &&
|
|
1001
|
+
!this.#isIdentifierChar(this.input.charCodeAt(next_non_whitespace + 5))
|
|
1002
|
+
) {
|
|
1003
|
+
const after_await = skip_whitespace_from(this.input, next_non_whitespace + 5);
|
|
1004
|
+
return this.input.charCodeAt(after_await) === CharCode.openParen;
|
|
1005
|
+
}
|
|
1006
|
+
return false;
|
|
1007
|
+
}
|
|
1008
|
+
return (word === 'if' || word === 'switch') && next === CharCode.openParen;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
#isJSXControlFlowDirectiveStart() {
|
|
1012
|
+
return this.#isJSXControlFlowDirectiveAt(this.start);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
/**
|
|
1016
|
+
* `@{ … }` code block: an `@` immediately followed by `{` at child/body
|
|
1017
|
+
* position. This is the marker that switches a body from plain JSX to a JS
|
|
1018
|
+
* code block (§2). Whitespace between `@` and `{` is not allowed — they must
|
|
1019
|
+
* be adjacent so it can never be confused with an `@directive` or a literal
|
|
1020
|
+
* `@` followed by an expression container.
|
|
1021
|
+
* @param {number} index
|
|
1022
|
+
*/
|
|
1023
|
+
#isCodeBlockStart(index) {
|
|
1024
|
+
return (
|
|
1025
|
+
this.input.charCodeAt(index) === CharCode.at &&
|
|
1026
|
+
this.input.charCodeAt(index + 1) === CharCode.openBrace
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
/**
|
|
1031
|
+
* True when the body position starting at `this.start` opens a `@{ … }`
|
|
1032
|
+
* code block, skipping leading whitespace.
|
|
1033
|
+
*/
|
|
1034
|
+
#atCodeBlockStart() {
|
|
1035
|
+
const index = skip_whitespace_from(this.input, this.start);
|
|
1036
|
+
return this.#isCodeBlockStart(index);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* @param {AST.Node | null | undefined} node
|
|
1041
|
+
*/
|
|
1042
|
+
#isRenderOutputNode(node) {
|
|
1043
|
+
if (!node) return false;
|
|
1044
|
+
switch (node.type) {
|
|
1045
|
+
case 'JSXElement':
|
|
1046
|
+
case 'JSXFragment':
|
|
1047
|
+
case 'JSXStyleElement':
|
|
1048
|
+
case 'JSXCodeBlock':
|
|
1049
|
+
case 'JSXIfExpression':
|
|
1050
|
+
case 'JSXForExpression':
|
|
1051
|
+
case 'JSXSwitchExpression':
|
|
1052
|
+
case 'JSXTryExpression':
|
|
1053
|
+
return true;
|
|
1054
|
+
}
|
|
1055
|
+
return false;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
/**
|
|
1059
|
+
* Inside a code block (`@{ … }` or a directive's `{ }`), decides whether the
|
|
1060
|
+
* next thing is the single bare render node (`<tag …>`, `<>…</>`, or an
|
|
1061
|
+
* `@if`/`@for`/`@switch`/`@try` directive) rather than a setup statement.
|
|
1062
|
+
*
|
|
1063
|
+
* Render output that begins with `<` is recognized by the tokenizer
|
|
1064
|
+
* (`getTokenFromCode`): it emits `jsxTagStart` for a `<` that opens a tag — at
|
|
1065
|
+
* the start of a line, or in an expression position such as after `;`/`{`/`=>` —
|
|
1066
|
+
* which the `jsxTagStart` fast path below covers. The char-based fallback for a
|
|
1067
|
+
* raw `<` therefore only treats it as render output when the tag starts its own
|
|
1068
|
+
* line or follows a `;` on the same line (so one-liners such as
|
|
1069
|
+
* `@{ const foo = 1; <>{foo}</> }` work). A `<` the tokenizer left as a
|
|
1070
|
+
* relational operator while trailing a value on the same line is the comparison
|
|
1071
|
+
* it looks like (`aaa <b` is `aaa < b`, never a `<b>` tag), so it stays setup
|
|
1072
|
+
* code rather than being mistaken for render output.
|
|
1073
|
+
*/
|
|
1074
|
+
#atRenderNodeStart() {
|
|
1075
|
+
if (this.type === tstt.jsxTagStart) return true;
|
|
1076
|
+
const index = skip_whitespace_from(this.input, this.start);
|
|
1077
|
+
const ch = this.input.charCodeAt(index);
|
|
1078
|
+
if (ch === CharCode.lessThan) {
|
|
1079
|
+
const next = this.input.charCodeAt(index + 1);
|
|
1080
|
+
if (next === CharCode.slash) return false;
|
|
1081
|
+
const tagLike =
|
|
1082
|
+
next === CharCode.greaterThan ||
|
|
1083
|
+
next === CharCode.at ||
|
|
1084
|
+
next === CharCode.dollar ||
|
|
1085
|
+
next === CharCode.underscore ||
|
|
1086
|
+
(next >= CharCode.uppercaseA && next <= CharCode.uppercaseZ) ||
|
|
1087
|
+
(next >= CharCode.lowercaseA && next <= CharCode.lowercaseZ);
|
|
1088
|
+
const previous = this.#previousNonSpaceTabIndex(index);
|
|
1089
|
+
const afterSemicolon =
|
|
1090
|
+
previous >= 0 && this.input.charCodeAt(previous) === CharCode.semicolon;
|
|
1091
|
+
return tagLike && (this.#isLineStartPosition(index) || afterSemicolon);
|
|
1092
|
+
}
|
|
1093
|
+
return this.#isCodeBlockStart(index) || this.#isJSXControlFlowDirectiveAt(index);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* Parse one setup statement inside a code block as ordinary TS, with the
|
|
1098
|
+
* native-template path hidden so `<` reads as a relational/type operator
|
|
1099
|
+
* (`value < limit`, `foo<T>()`) rather than a JSX tag, and any JSX value
|
|
1100
|
+
* (`const x = <div/>`) parses as a plain JSX expression.
|
|
1101
|
+
*/
|
|
1102
|
+
#parseCodeBlockSetupStatement() {
|
|
1103
|
+
const previous_context = this.context;
|
|
1104
|
+
this.context = previous_context.filter(
|
|
1105
|
+
(context) =>
|
|
1106
|
+
context !== tstc.tc_expr && context !== tstc.tc_oTag && context !== tstc.tc_cTag,
|
|
1107
|
+
);
|
|
1108
|
+
let pushed_statement_context = false;
|
|
1109
|
+
if (this.curContext() !== b_stat) {
|
|
1110
|
+
this.context.push(b_stat);
|
|
1111
|
+
pushed_statement_context = true;
|
|
1112
|
+
}
|
|
1113
|
+
this.exprAllowed = true;
|
|
1114
|
+
const previous_path = this.#path;
|
|
1115
|
+
this.#path = [];
|
|
1116
|
+
this.#templateScriptParsingDepth++;
|
|
1117
|
+
let node;
|
|
1118
|
+
try {
|
|
1119
|
+
// A code-block/directive body is statements plus at most one render node —
|
|
1120
|
+
// never bare text or markup tokens. If the tokenizer mis-read trailing
|
|
1121
|
+
// code as JSX (raw text or a tag-name token — both can happen for a
|
|
1122
|
+
// statement following the render node, depending on the leftover context),
|
|
1123
|
+
// reposition to the token start and re-read it as code now that the
|
|
1124
|
+
// template path is hidden. It then parses as a statement so the
|
|
1125
|
+
// one-render-node rule reports a clear "statements cannot follow" error
|
|
1126
|
+
// instead of a generic parse fault.
|
|
1127
|
+
if (this.type === tstt.jsxText || this.type === tstt.jsxName) {
|
|
1128
|
+
// Rewinding `pos` to the mis-read token's start must also rewind the
|
|
1129
|
+
// line counter: a `jsxText` token can span newlines (e.g. the blank
|
|
1130
|
+
// line before a following render node), and reading it already
|
|
1131
|
+
// advanced `curLine`/`lineStart` to its end. Resetting only `pos`
|
|
1132
|
+
// would leave the line counter ahead of `pos`, inflating the `loc`
|
|
1133
|
+
// of this statement and every node after it (which crashes source-map
|
|
1134
|
+
// mapping when the inflated end line runs past the file).
|
|
1135
|
+
const loc = acorn.getLineInfo(this.input, this.start);
|
|
1136
|
+
this.pos = this.start;
|
|
1137
|
+
this.curLine = loc.line;
|
|
1138
|
+
this.lineStart = this.start - loc.column;
|
|
1139
|
+
this.nextToken();
|
|
1140
|
+
}
|
|
1141
|
+
node = this.parseStatement(null);
|
|
1142
|
+
} finally {
|
|
1143
|
+
this.#templateScriptParsingDepth--;
|
|
1144
|
+
this.#path = previous_path;
|
|
1145
|
+
if (pushed_statement_context && this.curContext() === b_stat) {
|
|
1146
|
+
this.context.pop();
|
|
1147
|
+
}
|
|
1148
|
+
this.context = previous_context;
|
|
1149
|
+
}
|
|
1150
|
+
if (this.curContext() === tstc.tc_expr) {
|
|
1151
|
+
this.context.pop();
|
|
1152
|
+
}
|
|
1153
|
+
return node;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
/**
|
|
1157
|
+
* Parse the single bare render node of a code block — a JSX element/fragment
|
|
1158
|
+
* (parsed as a native TSRX element so its own body may again be plain JSX or
|
|
1159
|
+
* a nested `@{ … }`) or an `@if`/`@for`/`@switch`/`@try` directive.
|
|
1160
|
+
*/
|
|
1161
|
+
#parseCodeBlockRenderNode() {
|
|
1162
|
+
const at_index = skip_whitespace_from(this.input, this.start);
|
|
1163
|
+
// Reposition onto the render token so it re-tokenizes in a clean context
|
|
1164
|
+
// (a preceding setup statement's context restore can strip the JSX tag
|
|
1165
|
+
// contexts the trailing `<`/`@` token first pushed).
|
|
1166
|
+
if (this.start !== at_index) {
|
|
1167
|
+
const loc = acorn.getLineInfo(this.input, at_index);
|
|
1168
|
+
this.pos = at_index;
|
|
1169
|
+
this.start = at_index;
|
|
1170
|
+
this.startLoc = new acorn.Position(loc.line, loc.column);
|
|
1171
|
+
this.curLine = loc.line;
|
|
1172
|
+
this.lineStart = at_index - loc.column;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
if (this.#isCodeBlockStart(at_index)) {
|
|
1176
|
+
return /** @type {AST.Node} */ (/** @type {unknown} */ (this.#parseCodeBlock()));
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
if (this.#isJSXControlFlowDirectiveAt(at_index)) {
|
|
1180
|
+
return /** @type {AST.Node} */ (
|
|
1181
|
+
/** @type {unknown} */ (this.#parseJSXControlFlowExpression())
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// Re-read the `<` so its `jsxTagStart` pushes the opening-tag contexts.
|
|
1186
|
+
this.pos = at_index;
|
|
1187
|
+
this.exprAllowed = true;
|
|
1188
|
+
this.next();
|
|
1189
|
+
if (this.type !== tstt.jsxTagStart) {
|
|
1190
|
+
this.unexpected();
|
|
1191
|
+
}
|
|
1192
|
+
this.next();
|
|
1193
|
+
if (this.value === '/' || this.type === tt.slash) {
|
|
1194
|
+
this.unexpected();
|
|
1195
|
+
}
|
|
1196
|
+
const node = this.parseElement();
|
|
1197
|
+
if (!node) {
|
|
1198
|
+
this.unexpected();
|
|
1199
|
+
}
|
|
1200
|
+
if (this.curContext() === tstc.tc_expr) {
|
|
1201
|
+
this.context.pop();
|
|
1202
|
+
}
|
|
1203
|
+
return /** @type {AST.Node} */ (/** @type {unknown} */ (node));
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
/**
|
|
1207
|
+
* Shared `Statement* RenderOutput?` grammar for the body of a `@{ … }` code
|
|
1208
|
+
* block and the `{ }` of an `@if`/`@for`/`@switch`/`@try` directive (§2
|
|
1209
|
+
* rules 4–8). Fills `flat` with the setup statements followed by at most one
|
|
1210
|
+
* trailing render node. Leaves the tokenizer positioned at the closing `}`.
|
|
1211
|
+
* @param {AST.Node[]} flat
|
|
1212
|
+
*/
|
|
1213
|
+
#parseCodeBlockBody(flat) {
|
|
1214
|
+
let render_seen = false;
|
|
1215
|
+
while (this.type !== tt.braceR && this.type !== tt.eof) {
|
|
1216
|
+
// A bare `;` is an empty statement carrying no meaning. JSX render
|
|
1217
|
+
// output does not consume a trailing `;`, so one written after the
|
|
1218
|
+
// render node (`<>…</>;`) would otherwise parse as a statement and
|
|
1219
|
+
// trip the "statements cannot follow the rendered output" rule. Skip
|
|
1220
|
+
// stray semicolons silently here; prettier strips them on format.
|
|
1221
|
+
if (this.type === tt.semi) {
|
|
1222
|
+
this.next();
|
|
1223
|
+
continue;
|
|
1224
|
+
}
|
|
1225
|
+
if (this.#atRenderNodeStart()) {
|
|
1226
|
+
const render_node = this.#parseCodeBlockRenderNode();
|
|
1227
|
+
if (render_seen) {
|
|
1228
|
+
this.#report_recoverable_error_range(
|
|
1229
|
+
/** @type {number} */ (render_node.start),
|
|
1230
|
+
/** @type {number} */ (render_node.end),
|
|
1231
|
+
"A code block renders a single node; wrap multiple nodes or text in a fragment '<>…</>'.",
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
flat.push(render_node);
|
|
1235
|
+
render_seen = true;
|
|
1236
|
+
continue;
|
|
1237
|
+
}
|
|
1238
|
+
const statement = this.#parseCodeBlockSetupStatement();
|
|
1239
|
+
if (statement) {
|
|
1240
|
+
if (render_seen) {
|
|
1241
|
+
// A statement after the rendered output: code must come first.
|
|
1242
|
+
this.#report_recoverable_error_range(
|
|
1243
|
+
/** @type {number} */ (statement.start),
|
|
1244
|
+
/** @type {number} */ (statement.end),
|
|
1245
|
+
"Code must be at the top of '@{ }'; statements cannot follow the rendered output.",
|
|
1246
|
+
);
|
|
1247
|
+
}
|
|
1248
|
+
flat.push(statement);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
/**
|
|
1254
|
+
* Parse an explicit `@{ … }` code block (`this.start` at `@`). Returns a
|
|
1255
|
+
* `JSXCodeBlock` whose `body` holds the setup statements and `render` the
|
|
1256
|
+
* single optional render output (§9).
|
|
1257
|
+
*/
|
|
1258
|
+
#parseCodeBlock() {
|
|
1259
|
+
const start = this.start;
|
|
1260
|
+
const startLoc = this.startLoc;
|
|
1261
|
+
const node = /** @type {AST.JSXCodeBlock} */ (this.startNodeAt(start, startLoc));
|
|
1262
|
+
node.body = [];
|
|
1263
|
+
node.render = null;
|
|
1264
|
+
node.metadata = { path: [] };
|
|
1265
|
+
|
|
1266
|
+
// The body parses as JS, so swap the surrounding JSX/template token
|
|
1267
|
+
// contexts for a clean statement context and hide the enclosing template
|
|
1268
|
+
// from `#path` so the body tokenizes as code (not JSX raw text). Both are
|
|
1269
|
+
// restored before the closing `}` is consumed so the following `</tag>`
|
|
1270
|
+
// tokenizes against the same template context the body opened in.
|
|
1271
|
+
const enclosing_context = this.context;
|
|
1272
|
+
const enclosing_path = this.#path;
|
|
1273
|
+
const braceStart = start + 1;
|
|
1274
|
+
this.context = enclosing_context.filter(
|
|
1275
|
+
(context) =>
|
|
1276
|
+
context !== tstc.tc_expr && context !== tstc.tc_oTag && context !== tstc.tc_cTag,
|
|
1277
|
+
);
|
|
1278
|
+
if (this.curContext() !== b_stat) {
|
|
1279
|
+
this.context.push(b_stat);
|
|
1280
|
+
}
|
|
1281
|
+
const braceLoc = acorn.getLineInfo(this.input, braceStart);
|
|
1282
|
+
this.pos = braceStart;
|
|
1283
|
+
this.start = braceStart;
|
|
1284
|
+
this.startLoc = new acorn.Position(braceLoc.line, braceLoc.column);
|
|
1285
|
+
this.curLine = braceLoc.line;
|
|
1286
|
+
this.lineStart = braceStart - braceLoc.column;
|
|
1287
|
+
this.exprAllowed = true;
|
|
1288
|
+
this.#path = [];
|
|
1289
|
+
this.next();
|
|
1290
|
+
this.expect(tt.braceL);
|
|
1291
|
+
|
|
1292
|
+
/** @type {AST.Node[]} */
|
|
1293
|
+
const flat = [];
|
|
1294
|
+
this.enterScope(0);
|
|
1295
|
+
try {
|
|
1296
|
+
this.#parseCodeBlockBody(flat);
|
|
1297
|
+
} finally {
|
|
1298
|
+
this.exitScope();
|
|
1299
|
+
this.#path = enclosing_path;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
const last = flat[flat.length - 1];
|
|
1303
|
+
if (this.#isRenderOutputNode(last)) {
|
|
1304
|
+
node.render = last;
|
|
1305
|
+
node.body = /** @type {AST.Statement[]} */ (flat.slice(0, -1));
|
|
1306
|
+
} else {
|
|
1307
|
+
node.body = /** @type {AST.Statement[]} */ (flat);
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
if (this.type !== tt.braceR) {
|
|
1311
|
+
this.unexpected();
|
|
1312
|
+
}
|
|
1313
|
+
// Restore the enclosing template context, then consume `}` and read the
|
|
1314
|
+
// following token (typically the parent's `</tag>`) against it. Finish the
|
|
1315
|
+
// node after the `}` so its range spans the whole `@{ … }` (this is what
|
|
1316
|
+
// lets trailing comments before `}` attach to the block, not the parent's
|
|
1317
|
+
// closing tag).
|
|
1318
|
+
const brace_close_end = this.end;
|
|
1319
|
+
const brace_close_end_loc = this.endLoc;
|
|
1320
|
+
this.context = enclosing_context;
|
|
1321
|
+
this.next();
|
|
1322
|
+
this.finishNodeAt(node, 'JSXCodeBlock', brace_close_end, brace_close_end_loc);
|
|
1323
|
+
return node;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
/**
|
|
1327
|
+
* At-sign constructs are expressions (§6a, §2 rule 9): code blocks and the
|
|
1328
|
+
* if/for/switch/try directive forms may be returned, assigned, or passed
|
|
1329
|
+
* anywhere an expression is expected. Only code blocks and the four reserved
|
|
1330
|
+
* control-flow keywords are intercepted; any other at-sign form, such as a
|
|
1331
|
+
* decorated class expression, falls through so decorators keep working.
|
|
1332
|
+
* @type {Parse.Parser['parseExprAtom']}
|
|
1333
|
+
*/
|
|
1334
|
+
parseExprAtom(refDestructuringErrors, forInit, forNew) {
|
|
1335
|
+
if (this.input.charCodeAt(this.start) === CharCode.at) {
|
|
1336
|
+
if (this.#isCodeBlockStart(this.start)) {
|
|
1337
|
+
return /** @type {any} */ (this.#parseCodeBlock());
|
|
1338
|
+
}
|
|
1339
|
+
if (this.#isJSXControlFlowDirectiveAt(this.start)) {
|
|
1340
|
+
return /** @type {any} */ (this.#parseJSXControlFlowExpression());
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
return super.parseExprAtom(refDestructuringErrors, forInit, forNew);
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
/**
|
|
1347
|
+
* @param {AST.Node} node
|
|
1348
|
+
* @param {string} type
|
|
1349
|
+
* @param {number} start
|
|
1350
|
+
* @param {AST.Position} startLoc
|
|
1351
|
+
*/
|
|
1352
|
+
#finishJSXControlFlowExpression(node, type, start, startLoc) {
|
|
1353
|
+
node.start = start;
|
|
1354
|
+
/** @type {AST.NodeWithLocation} */ (node).loc.start = startLoc;
|
|
1355
|
+
node.metadata ??= { path: [] };
|
|
1356
|
+
/** @type {any} */ (node).statementType = node.type;
|
|
1357
|
+
/** @type {any} */ (node).type = type;
|
|
1358
|
+
return node;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
#parseJSXControlFlowExpression() {
|
|
1362
|
+
const start = this.start;
|
|
1363
|
+
const startLoc = this.startLoc;
|
|
1364
|
+
const keywordStart = start + 1;
|
|
1365
|
+
this.pos = keywordStart;
|
|
1366
|
+
this.start = keywordStart;
|
|
1367
|
+
this.startLoc = acorn.getLineInfo(this.input, keywordStart);
|
|
1368
|
+
this.#readingJSXControlFlowDirectiveKeyword = true;
|
|
1369
|
+
try {
|
|
1370
|
+
this.nextToken();
|
|
1371
|
+
} finally {
|
|
1372
|
+
this.#readingJSXControlFlowDirectiveKeyword = false;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
const label = this.type.keyword || this.type.label || this.value;
|
|
1376
|
+
if (label === 'if') {
|
|
1377
|
+
return this.#finishJSXControlFlowExpression(
|
|
1378
|
+
this.#parseTemplateIfStatement(),
|
|
1379
|
+
'JSXIfExpression',
|
|
1380
|
+
start,
|
|
1381
|
+
startLoc,
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
if (label === 'for') {
|
|
1386
|
+
this.#templateControlFlowBlockDepth++;
|
|
1387
|
+
let node;
|
|
1388
|
+
const previous_reading_header = this.#readingJSXControlFlowHeader;
|
|
1389
|
+
this.#readingJSXControlFlowHeader = true;
|
|
1390
|
+
try {
|
|
1391
|
+
node = this.#finishJSXControlFlowExpression(
|
|
1392
|
+
this.parseStatement(null),
|
|
1393
|
+
'JSXForExpression',
|
|
1394
|
+
start,
|
|
1395
|
+
startLoc,
|
|
1396
|
+
);
|
|
1397
|
+
} finally {
|
|
1398
|
+
this.#readingJSXControlFlowHeader = previous_reading_header;
|
|
1399
|
+
this.#templateControlFlowBlockDepth--;
|
|
1400
|
+
}
|
|
1401
|
+
if (
|
|
1402
|
+
/** @type {any} */ (node).statementType !== 'ForOfStatement' &&
|
|
1403
|
+
/** @type {any} */ (node).statementType !== 'ForInStatement' &&
|
|
1404
|
+
/** @type {any} */ (node).statementType !== 'ForStatement'
|
|
1405
|
+
) {
|
|
1406
|
+
this.raise(start, 'Expected `for` after `@`.');
|
|
1407
|
+
}
|
|
1408
|
+
if (/** @type {any} */ (node).body?.type !== 'BlockStatement') {
|
|
1409
|
+
this.raise(
|
|
1410
|
+
/** @type {any} */ (node).body?.start ?? start,
|
|
1411
|
+
'Expected `{` after JSX control-flow directive.',
|
|
1412
|
+
);
|
|
1413
|
+
}
|
|
1414
|
+
if (this.#eatJSXForEmptyKeyword()) {
|
|
1415
|
+
if (this.type !== tt.braceL) {
|
|
1416
|
+
this.raise(this.start, 'Expected `{` after JSX control-flow directive.');
|
|
1417
|
+
}
|
|
1418
|
+
this.#templateControlFlowBlockDepth++;
|
|
1419
|
+
try {
|
|
1420
|
+
/** @type {any} */ (node).empty = this.parseBlock();
|
|
1421
|
+
} finally {
|
|
1422
|
+
this.#templateControlFlowBlockDepth--;
|
|
1423
|
+
}
|
|
1424
|
+
} else if (this.#isUnprefixedDirectiveClauseKeyword('empty')) {
|
|
1425
|
+
this.raise(this.start, 'Expected `@empty` after `@for` block.');
|
|
1426
|
+
} else {
|
|
1427
|
+
/** @type {any} */ (node).empty = null;
|
|
1428
|
+
}
|
|
1429
|
+
return node;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
if (label === 'switch') {
|
|
1433
|
+
return this.#parseJSXSwitchExpression(start, startLoc);
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
if (label === 'try') {
|
|
1437
|
+
this.#templateControlFlowTryDepth++;
|
|
1438
|
+
try {
|
|
1439
|
+
return this.#finishJSXControlFlowExpression(
|
|
1440
|
+
this.parseStatement(null),
|
|
1441
|
+
'JSXTryExpression',
|
|
1442
|
+
start,
|
|
1443
|
+
startLoc,
|
|
1444
|
+
);
|
|
1445
|
+
} finally {
|
|
1446
|
+
this.#templateControlFlowTryDepth--;
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
this.raise(start, 'Expected `@if`, `@for`, `@switch`, or `@try`.');
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
/**
|
|
1454
|
+
* @param {string} keyword
|
|
1455
|
+
*/
|
|
1456
|
+
#eatJSXDirectiveClauseKeyword(keyword) {
|
|
1457
|
+
const keywordStart = skip_whitespace_from(this.input, this.start);
|
|
1458
|
+
if (this.input.charCodeAt(keywordStart) !== CharCode.at) {
|
|
1459
|
+
return false;
|
|
1460
|
+
}
|
|
1461
|
+
const wordStart = keywordStart + 1;
|
|
1462
|
+
if (
|
|
1463
|
+
this.input.slice(wordStart, wordStart + keyword.length) !== keyword ||
|
|
1464
|
+
this.#isIdentifierChar(this.input.charCodeAt(wordStart + keyword.length))
|
|
1465
|
+
) {
|
|
1466
|
+
return false;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
this.pos = wordStart;
|
|
1470
|
+
this.start = wordStart;
|
|
1471
|
+
this.startLoc = acorn.getLineInfo(this.input, wordStart);
|
|
1472
|
+
this.curLine = this.startLoc.line;
|
|
1473
|
+
this.lineStart = wordStart - this.startLoc.column;
|
|
1474
|
+
this.context = this.context.filter(
|
|
1475
|
+
(context) =>
|
|
1476
|
+
context !== tstc.tc_expr && context !== tstc.tc_oTag && context !== tstc.tc_cTag,
|
|
1477
|
+
);
|
|
1478
|
+
if (this.curContext() !== b_stat) {
|
|
1479
|
+
this.context.push(b_stat);
|
|
1480
|
+
}
|
|
1481
|
+
this.exprAllowed = true;
|
|
1482
|
+
this.#readingJSXControlFlowDirectiveKeyword = true;
|
|
1483
|
+
try {
|
|
1484
|
+
this.nextToken();
|
|
1485
|
+
} finally {
|
|
1486
|
+
this.#readingJSXControlFlowDirectiveKeyword = false;
|
|
1487
|
+
}
|
|
1488
|
+
this.next();
|
|
1489
|
+
return true;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
#eatJSXForEmptyKeyword() {
|
|
1493
|
+
return this.#eatJSXDirectiveClauseKeyword('empty');
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
/**
|
|
1497
|
+
* @param {string} keyword
|
|
1498
|
+
*/
|
|
1499
|
+
#eatJSXDirectiveBareClauseKeyword(keyword) {
|
|
1500
|
+
const wordStart = skip_whitespace_from(this.input, this.start);
|
|
1501
|
+
if (
|
|
1502
|
+
this.input.slice(wordStart, wordStart + keyword.length) !== keyword ||
|
|
1503
|
+
this.#isIdentifierChar(this.input.charCodeAt(wordStart + keyword.length))
|
|
1504
|
+
) {
|
|
1505
|
+
return false;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
this.pos = wordStart;
|
|
1509
|
+
this.start = wordStart;
|
|
1510
|
+
this.startLoc = acorn.getLineInfo(this.input, wordStart);
|
|
1511
|
+
this.curLine = this.startLoc.line;
|
|
1512
|
+
this.lineStart = wordStart - this.startLoc.column;
|
|
1513
|
+
this.context = this.context.filter(
|
|
1514
|
+
(context) =>
|
|
1515
|
+
context !== tstc.tc_expr && context !== tstc.tc_oTag && context !== tstc.tc_cTag,
|
|
1516
|
+
);
|
|
1517
|
+
if (this.curContext() !== b_stat) {
|
|
1518
|
+
this.context.push(b_stat);
|
|
1519
|
+
}
|
|
1520
|
+
this.exprAllowed = true;
|
|
1521
|
+
this.#readingJSXControlFlowDirectiveKeyword = true;
|
|
1522
|
+
try {
|
|
1523
|
+
this.nextToken();
|
|
1524
|
+
} finally {
|
|
1525
|
+
this.#readingJSXControlFlowDirectiveKeyword = false;
|
|
1526
|
+
}
|
|
1527
|
+
return true;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
/**
|
|
1531
|
+
* @param {string} keyword
|
|
1532
|
+
*/
|
|
1533
|
+
#isUnprefixedDirectiveClauseKeyword(keyword) {
|
|
1534
|
+
const keywordStart = skip_whitespace_from(this.input, this.start);
|
|
1535
|
+
return (
|
|
1536
|
+
this.input.slice(keywordStart, keywordStart + keyword.length) === keyword &&
|
|
1537
|
+
!this.#isIdentifierChar(this.input.charCodeAt(keywordStart + keyword.length))
|
|
1538
|
+
);
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
/**
|
|
1542
|
+
* @returns {'case' | 'default' | null}
|
|
1543
|
+
*/
|
|
1544
|
+
#eatJSXSwitchCaseClauseKeyword() {
|
|
1545
|
+
if (this.#eatJSXDirectiveClauseKeyword('case')) {
|
|
1546
|
+
return 'case';
|
|
1547
|
+
}
|
|
1548
|
+
if (this.#eatJSXDirectiveClauseKeyword('default')) {
|
|
1549
|
+
return 'default';
|
|
1550
|
+
}
|
|
1551
|
+
return null;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
#parseTemplateControlFlowStatement() {
|
|
1555
|
+
if (this.type !== tt.braceL) {
|
|
1556
|
+
this.raise(this.start, 'Expected `{` after JSX control-flow directive.');
|
|
1557
|
+
}
|
|
1558
|
+
return this.#parseTemplateControlFlowBlock();
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
#parseTemplateIfStatement() {
|
|
1562
|
+
const node = /** @type {AST.IfStatement} */ (this.startNode());
|
|
1563
|
+
const previous_reading_header = this.#readingJSXControlFlowHeader;
|
|
1564
|
+
this.#readingJSXControlFlowHeader = true;
|
|
1565
|
+
try {
|
|
1566
|
+
this.next();
|
|
1567
|
+
node.test = this.parseParenExpression();
|
|
1568
|
+
} finally {
|
|
1569
|
+
this.#readingJSXControlFlowHeader = previous_reading_header;
|
|
1570
|
+
}
|
|
1571
|
+
node.consequent = /** @type {AST.Statement} */ (this.#parseTemplateControlFlowStatement());
|
|
1572
|
+
node.alternate = null;
|
|
1573
|
+
|
|
1574
|
+
if (this.#eatJSXDirectiveClauseKeyword('else')) {
|
|
1575
|
+
node.alternate = this.#eatJSXDirectiveBareClauseKeyword('if')
|
|
1576
|
+
? this.#parseTemplateIfStatement()
|
|
1577
|
+
: /** @type {AST.Statement} */ (this.#parseTemplateControlFlowStatement());
|
|
1578
|
+
} else if (this.#isUnprefixedDirectiveClauseKeyword('else')) {
|
|
1579
|
+
this.raise(this.start, 'Expected `@else` after `@if` block.');
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
return this.finishNode(node, 'IfStatement');
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
/**
|
|
1586
|
+
* @param {number} start
|
|
1587
|
+
* @param {AST.Position} startLoc
|
|
1588
|
+
*/
|
|
1589
|
+
#parseJSXSwitchExpression(start, startLoc) {
|
|
1590
|
+
const node = /** @type {AST.SwitchStatement} */ (this.startNodeAt(start, startLoc));
|
|
1591
|
+
const previous_reading_header = this.#readingJSXControlFlowHeader;
|
|
1592
|
+
this.#readingJSXControlFlowHeader = true;
|
|
1593
|
+
try {
|
|
1594
|
+
this.next();
|
|
1595
|
+
node.discriminant = this.parseParenExpression();
|
|
1596
|
+
} finally {
|
|
1597
|
+
this.#readingJSXControlFlowHeader = previous_reading_header;
|
|
1598
|
+
}
|
|
1599
|
+
node.cases = [];
|
|
1600
|
+
this.expect(tt.braceL);
|
|
1601
|
+
this.labels.push({ kind: 'switch' });
|
|
1602
|
+
this.enterScope(0);
|
|
1603
|
+
|
|
1604
|
+
let sawDefault = false;
|
|
1605
|
+
while (this.type !== tt.braceR) {
|
|
1606
|
+
if (this.type === tstt.jsxText && this.#rewindToSwitchCaseLabel()) {
|
|
1607
|
+
continue;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
const clauseStart = this.start;
|
|
1611
|
+
const clauseStartLoc = this.startLoc;
|
|
1612
|
+
const clause = this.#eatJSXSwitchCaseClauseKeyword();
|
|
1613
|
+
if (clause) {
|
|
1614
|
+
const isCase = clause === 'case';
|
|
1615
|
+
const current = /** @type {AST.SwitchCase} */ (
|
|
1616
|
+
this.startNodeAt(clauseStart, clauseStartLoc)
|
|
1617
|
+
);
|
|
1618
|
+
current.consequent = [];
|
|
1619
|
+
const previous_reading_header = this.#readingJSXControlFlowHeader;
|
|
1620
|
+
this.#readingJSXControlFlowHeader = true;
|
|
1621
|
+
try {
|
|
1622
|
+
if (isCase) {
|
|
1623
|
+
current.test = this.parseExpression();
|
|
1624
|
+
} else {
|
|
1625
|
+
if (sawDefault) {
|
|
1626
|
+
this.raiseRecoverable(this.lastTokStart, 'Multiple default clauses');
|
|
1627
|
+
}
|
|
1628
|
+
sawDefault = true;
|
|
1629
|
+
current.test = null;
|
|
1630
|
+
}
|
|
1631
|
+
this.expect(tt.colon);
|
|
1632
|
+
} finally {
|
|
1633
|
+
this.#readingJSXControlFlowHeader = previous_reading_header;
|
|
1634
|
+
}
|
|
1635
|
+
this.expect(tt.braceL);
|
|
1636
|
+
while (this.type !== tt.braceR) {
|
|
1637
|
+
this.#parseJSXSwitchCaseConsequent(current.consequent);
|
|
1638
|
+
}
|
|
1639
|
+
this.expect(tt.braceR);
|
|
1640
|
+
node.cases.push(this.finishNode(current, 'SwitchCase'));
|
|
1641
|
+
continue;
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
this.unexpected();
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
this.exitScope();
|
|
1648
|
+
this.next();
|
|
1649
|
+
this.labels.pop();
|
|
1650
|
+
return this.#finishJSXControlFlowExpression(
|
|
1651
|
+
this.finishNode(node, 'SwitchStatement'),
|
|
1652
|
+
'JSXSwitchExpression',
|
|
1653
|
+
start,
|
|
1654
|
+
startLoc,
|
|
1655
|
+
);
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
/**
|
|
1659
|
+
* @param {AST.Node[]} consequent
|
|
1660
|
+
* @this {TSRXParser & Parse.Parser}
|
|
1661
|
+
*/
|
|
1662
|
+
#parseJSXSwitchCaseConsequent(consequent) {
|
|
1663
|
+
if (this.type === tt.braceL) {
|
|
1664
|
+
consequent.push(this.#parseNativeTemplateExpressionContainer());
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
// A non-whitespace, non-directive case consequent that the tokenizer read
|
|
1669
|
+
// as raw text is a setup statement (in the new design bare text must be
|
|
1670
|
+
// wrapped in `<>`, so anything left here is code, e.g.
|
|
1671
|
+
// `props.status satisfies never`, `doThing()`, `x = 1`). Re-tokenize it as
|
|
1672
|
+
// JS and parse it as a statement instead of treating it as text.
|
|
1673
|
+
if (
|
|
1674
|
+
this.type === tstt.jsxText &&
|
|
1675
|
+
String(this.value ?? '').trim() !== '' &&
|
|
1676
|
+
!this.#isJSXControlFlowDirectiveStart() &&
|
|
1677
|
+
this.#switchCaseLabelStart(this.start) === -1
|
|
1678
|
+
) {
|
|
1679
|
+
const raw = String(this.value ?? '').trimStart();
|
|
1680
|
+
if (/^break\b/.test(raw)) {
|
|
1681
|
+
this.raise(this.start, '`break` is invalid inside `@switch` cases.');
|
|
1682
|
+
}
|
|
1683
|
+
if (/^return\b/.test(raw)) {
|
|
1684
|
+
this.raise(this.start, '`return` is invalid inside `@switch` cases.');
|
|
1685
|
+
}
|
|
1686
|
+
this.context = this.context.filter(
|
|
1687
|
+
(context) =>
|
|
1688
|
+
context !== tstc.tc_expr && context !== tstc.tc_oTag && context !== tstc.tc_cTag,
|
|
1689
|
+
);
|
|
1690
|
+
this.pos = this.start;
|
|
1691
|
+
this.startLoc = this.curPosition();
|
|
1692
|
+
if (this.curContext() !== b_stat) {
|
|
1693
|
+
this.context.push(b_stat);
|
|
1694
|
+
}
|
|
1695
|
+
this.exprAllowed = true;
|
|
1696
|
+
this.#parsingJSXSwitchCaseScriptStatementDepth++;
|
|
1697
|
+
try {
|
|
1698
|
+
this.#suppressTemplateRawTextToken = true;
|
|
1699
|
+
this.next();
|
|
1700
|
+
consequent.push(this.parseStatement(null));
|
|
1701
|
+
} finally {
|
|
1702
|
+
this.#parsingJSXSwitchCaseScriptStatementDepth--;
|
|
1703
|
+
}
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
if (this.type === tstt.jsxText) {
|
|
1708
|
+
const text = this.#parseJSXSwitchCaseRawText();
|
|
1709
|
+
if (!isWhitespaceTextNode(text)) {
|
|
1710
|
+
consequent.push(/** @type {any} */ (text));
|
|
1711
|
+
}
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
if (
|
|
1716
|
+
this.type === tstt.jsxTagStart ||
|
|
1717
|
+
this.input.charCodeAt(this.start) === CharCode.lessThan
|
|
1718
|
+
) {
|
|
1719
|
+
const startPos = this.start;
|
|
1720
|
+
const startLoc = this.startLoc;
|
|
1721
|
+
if (this.type === tstt.jsxTagStart) {
|
|
1722
|
+
this.next();
|
|
1723
|
+
} else {
|
|
1724
|
+
this.pos = startPos + 1;
|
|
1725
|
+
this.type = tstt.jsxTagStart;
|
|
1726
|
+
this.start = startPos;
|
|
1727
|
+
this.startLoc = startLoc;
|
|
1728
|
+
this.exprAllowed = false;
|
|
1729
|
+
this.next();
|
|
1730
|
+
}
|
|
1731
|
+
if (this.value === '/' || this.type === tt.slash) {
|
|
1732
|
+
this.unexpected();
|
|
1733
|
+
}
|
|
1734
|
+
const node = this.parseElement();
|
|
1735
|
+
if (!node) {
|
|
1736
|
+
this.unexpected();
|
|
1737
|
+
}
|
|
1738
|
+
consequent.push(/** @type {any} */ (node));
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
if (this.#isJSXControlFlowDirectiveStart()) {
|
|
1743
|
+
consequent.push(/** @type {any} */ (this.#parseJSXControlFlowExpression()));
|
|
1744
|
+
return;
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
if (this.#isSwitchCaseScriptStatementStart()) {
|
|
1748
|
+
this.#parsingJSXSwitchCaseScriptStatementDepth++;
|
|
1749
|
+
try {
|
|
1750
|
+
consequent.push(this.parseStatement(null));
|
|
1751
|
+
} finally {
|
|
1752
|
+
this.#parsingJSXSwitchCaseScriptStatementDepth--;
|
|
1753
|
+
}
|
|
1754
|
+
return;
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
const label = this.type.keyword || this.type.label;
|
|
1758
|
+
if (label === 'break') {
|
|
1759
|
+
this.raise(this.start, '`break` is invalid inside `@switch` cases.');
|
|
1760
|
+
}
|
|
1761
|
+
if (label === 'return') {
|
|
1762
|
+
this.raise(this.start, '`return` is invalid inside `@switch` cases.');
|
|
1763
|
+
}
|
|
1764
|
+
if (label === 'continue' || label === 'throw') {
|
|
1765
|
+
consequent.push(this.parseStatement(null));
|
|
1766
|
+
return;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
// Anything else here is JS read as ordinary tokens (e.g.
|
|
1770
|
+
// `props.status satisfies never`, `doThing()`): a setup statement, not text
|
|
1771
|
+
// (bare text in a case must be wrapped in `<>`). Clear the JSX/template
|
|
1772
|
+
// token contexts so the statement and the following `}`/`case` tokenize as
|
|
1773
|
+
// code.
|
|
1774
|
+
if (this.type !== tstt.jsxText && this.type !== tt.eof) {
|
|
1775
|
+
this.context = this.context.filter(
|
|
1776
|
+
(context) =>
|
|
1777
|
+
context !== tstc.tc_expr && context !== tstc.tc_oTag && context !== tstc.tc_cTag,
|
|
1778
|
+
);
|
|
1779
|
+
if (this.curContext() !== b_stat) {
|
|
1780
|
+
this.context.push(b_stat);
|
|
1781
|
+
}
|
|
1782
|
+
this.#parsingJSXSwitchCaseScriptStatementDepth++;
|
|
1783
|
+
try {
|
|
1784
|
+
consequent.push(this.parseStatement(null));
|
|
1785
|
+
} finally {
|
|
1786
|
+
this.#parsingJSXSwitchCaseScriptStatementDepth--;
|
|
1787
|
+
}
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
const text = this.#parseJSXSwitchCaseRawText();
|
|
1792
|
+
if (!isWhitespaceTextNode(text)) {
|
|
1793
|
+
consequent.push(text);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
/**
|
|
1798
|
+
* @param {ESTreeJSX.JSXOpeningElement} openingElement
|
|
1799
|
+
* @returns {ESTreeJSX.JSXOpeningFragment}
|
|
1800
|
+
*/
|
|
1801
|
+
#toOpeningFragment(openingElement) {
|
|
1802
|
+
const openingFragment = /** @type {ESTreeJSX.JSXOpeningFragment} */ (
|
|
1803
|
+
/** @type {unknown} */ (openingElement)
|
|
1804
|
+
);
|
|
1805
|
+
openingFragment.type = 'JSXOpeningFragment';
|
|
1806
|
+
delete (/** @type {any} */ (openingFragment).name);
|
|
1807
|
+
delete (/** @type {any} */ (openingFragment).attributes);
|
|
1808
|
+
delete (/** @type {any} */ (openingFragment).selfClosing);
|
|
1809
|
+
return openingFragment;
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
/**
|
|
1813
|
+
* @param {ESTreeJSX.JSXClosingElement} closingElement
|
|
1814
|
+
* @returns {ESTreeJSX.JSXClosingFragment}
|
|
1815
|
+
*/
|
|
1816
|
+
#toClosingFragment(closingElement) {
|
|
1817
|
+
const closingFragment = /** @type {ESTreeJSX.JSXClosingFragment} */ (
|
|
1818
|
+
/** @type {unknown} */ (closingElement)
|
|
1819
|
+
);
|
|
1820
|
+
closingFragment.type = 'JSXClosingFragment';
|
|
1821
|
+
delete (/** @type {any} */ (closingFragment).name);
|
|
1822
|
+
return closingFragment;
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
/**
|
|
1826
|
+
* @param {ESTreeJSX.JSXOpeningElement & AST.NodeWithLocation} open
|
|
1827
|
+
* @param {AST.JSXStyleElement} node
|
|
1828
|
+
* @param {boolean} insideHead
|
|
1829
|
+
*/
|
|
1830
|
+
#parseStyleElement(open, node, insideHead) {
|
|
1831
|
+
const contentStart = open.end;
|
|
1832
|
+
const input = this.input.slice(contentStart);
|
|
1833
|
+
const relativeCloseStart = input.indexOf('</style>');
|
|
1834
|
+
const content = relativeCloseStart === -1 ? input : input.slice(0, relativeCloseStart);
|
|
1835
|
+
const parsedCss = parse_style(content, { loose: this.#loose });
|
|
1836
|
+
|
|
1837
|
+
if (!insideHead) {
|
|
1838
|
+
node.metadata.styleScopeHash = parsedCss.hash;
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
const newLines = content.match(regex_newline_characters)?.length;
|
|
1842
|
+
if (newLines) {
|
|
1843
|
+
this.curLine = open.loc.end.line + newLines;
|
|
1844
|
+
this.lineStart = contentStart + content.lastIndexOf('\n') + 1;
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
if (relativeCloseStart !== -1) {
|
|
1848
|
+
const closingStart = contentStart + content.length;
|
|
1849
|
+
const closingLineInfo = acorn.getLineInfo(this.input, closingStart);
|
|
1850
|
+
const closingStartLoc = new acorn.Position(closingLineInfo.line, closingLineInfo.column);
|
|
1851
|
+
const nameStart = closingStart + 2;
|
|
1852
|
+
const nameEnd = nameStart + 'style'.length;
|
|
1853
|
+
const nameStartInfo = acorn.getLineInfo(this.input, nameStart);
|
|
1854
|
+
const nameEndInfo = acorn.getLineInfo(this.input, nameEnd);
|
|
1855
|
+
const name = /** @type {ESTreeJSX.JSXIdentifier} */ (
|
|
1856
|
+
this.startNodeAt(
|
|
1857
|
+
nameStart,
|
|
1858
|
+
new acorn.Position(nameStartInfo.line, nameStartInfo.column),
|
|
1859
|
+
)
|
|
1860
|
+
);
|
|
1861
|
+
name.name = 'style';
|
|
1862
|
+
name.tracked = false;
|
|
1863
|
+
this.finishNodeAt(
|
|
1864
|
+
name,
|
|
1865
|
+
'JSXIdentifier',
|
|
1866
|
+
nameEnd,
|
|
1867
|
+
new acorn.Position(nameEndInfo.line, nameEndInfo.column),
|
|
1868
|
+
);
|
|
1869
|
+
const closingEnd = closingStart + '</style>'.length;
|
|
1870
|
+
const closingEndInfo = acorn.getLineInfo(this.input, closingEnd);
|
|
1871
|
+
const closingElement = /** @type {ESTreeJSX.JSXClosingElement & AST.NodeWithLocation} */ (
|
|
1872
|
+
this.startNodeAt(closingStart, closingStartLoc)
|
|
1873
|
+
);
|
|
1874
|
+
closingElement.name = name;
|
|
1875
|
+
this.finishNodeAt(
|
|
1876
|
+
closingElement,
|
|
1877
|
+
'JSXClosingElement',
|
|
1878
|
+
closingEnd,
|
|
1879
|
+
new acorn.Position(closingEndInfo.line, closingEndInfo.column),
|
|
1880
|
+
);
|
|
1881
|
+
node.closingElement = closingElement;
|
|
1882
|
+
const parent = this.#path.at(-2);
|
|
1883
|
+
const insideTemplate = this.#isNativeTemplateNode(parent);
|
|
1884
|
+
if (this.curContext() === tstc.tc_expr && !insideTemplate) {
|
|
1885
|
+
this.context.pop();
|
|
1886
|
+
}
|
|
1887
|
+
this.exprAllowed = false;
|
|
1888
|
+
this.pos = closingEnd;
|
|
1889
|
+
this.curLine = closingEndInfo.line;
|
|
1890
|
+
this.lineStart = closingEnd - closingEndInfo.column;
|
|
1891
|
+
if (insideTemplate && relativeCloseStart === 0) {
|
|
1892
|
+
// Acorn has already tokenized the adjacent </style>; TSRX synthesizes
|
|
1893
|
+
// that close manually, so drop the stale style tag context.
|
|
1894
|
+
if (this.curContext() === tstc.tc_oTag) {
|
|
1895
|
+
this.context.pop();
|
|
1896
|
+
}
|
|
1897
|
+
if (this.curContext() === tstc.tc_expr) {
|
|
1898
|
+
this.context.pop();
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
if (!insideTemplate && this.#path.at(-1) === node) {
|
|
1902
|
+
this.#path.pop();
|
|
1903
|
+
try {
|
|
1904
|
+
this.next();
|
|
1905
|
+
} finally {
|
|
1906
|
+
this.#path.push(node);
|
|
1907
|
+
}
|
|
1908
|
+
} else {
|
|
1909
|
+
this.next();
|
|
1910
|
+
}
|
|
1911
|
+
} else {
|
|
1912
|
+
this.#report_broken_markup_error(
|
|
1913
|
+
open.end,
|
|
1914
|
+
"Unclosed tag '<style>'. Expected '</style>' before end of template.",
|
|
1915
|
+
);
|
|
1916
|
+
node.unclosed = true;
|
|
1917
|
+
}
|
|
531
1918
|
|
|
532
|
-
|
|
533
|
-
|
|
1919
|
+
node.css = content;
|
|
1920
|
+
node.children = [parsedCss];
|
|
534
1921
|
}
|
|
535
1922
|
|
|
536
|
-
#
|
|
537
|
-
const
|
|
538
|
-
this
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
text += this.input[this.pos];
|
|
550
|
-
this.pos++;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
if (!text) {
|
|
554
|
-
return null;
|
|
1923
|
+
#parseNativeTemplateExpressionContainer() {
|
|
1924
|
+
const allow_trailing_semicolon = this.#allowExpressionContainerTrailingSemicolon;
|
|
1925
|
+
this.#allowExpressionContainerTrailingSemicolon = true;
|
|
1926
|
+
// One-shot: marks this as a template *child* container (not an attribute
|
|
1927
|
+
// value or script-mode JSX child), so `jsx_parseExpressionContainer`
|
|
1928
|
+
// consumes the closing `}` after leaving container scope.
|
|
1929
|
+
this.#consumeContainerBraceAfterScope = true;
|
|
1930
|
+
let node;
|
|
1931
|
+
try {
|
|
1932
|
+
node = this.jsx_parseExpressionContainer();
|
|
1933
|
+
} finally {
|
|
1934
|
+
this.#allowExpressionContainerTrailingSemicolon = allow_trailing_semicolon;
|
|
1935
|
+
this.#consumeContainerBraceAfterScope = false;
|
|
555
1936
|
}
|
|
556
|
-
|
|
557
|
-
return /** @type {ESTreeJSX.JSXText} */ ({
|
|
558
|
-
type: 'JSXText',
|
|
559
|
-
value: text,
|
|
560
|
-
raw: text,
|
|
561
|
-
start,
|
|
562
|
-
end: this.pos,
|
|
563
|
-
});
|
|
1937
|
+
return /** @type {ESTreeJSX.JSXExpressionContainer} */ (/** @type {unknown} */ (node));
|
|
564
1938
|
}
|
|
565
1939
|
|
|
566
|
-
#
|
|
1940
|
+
#popTemplateTokenContextBeforeExpressionChild() {
|
|
567
1941
|
let index = this.pos;
|
|
568
1942
|
let has_newline = false;
|
|
569
1943
|
|
|
570
|
-
//
|
|
1944
|
+
// JSXText-only template fragments can leave the tokenizer in JSX text mode.
|
|
571
1945
|
// Only unwind it for ASI before a following TSRX `{expr}` child;
|
|
572
1946
|
// fragment props like `content={<></>}` still need the JSX context.
|
|
573
1947
|
while (index < this.input.length) {
|
|
@@ -664,19 +2038,27 @@ export function TSRXPlugin(config) {
|
|
|
664
2038
|
}
|
|
665
2039
|
|
|
666
2040
|
/**
|
|
667
|
-
* @param {
|
|
2041
|
+
* @param {ESTreeJSX.JSXElement | ESTreeJSX.JSXFragment} node
|
|
668
2042
|
* @returns {boolean}
|
|
669
2043
|
*/
|
|
670
2044
|
#hasDirectStatementChild(node) {
|
|
671
|
-
|
|
2045
|
+
const children = /** @type {AST.Node[]} */ (/** @type {unknown} */ (node.children ?? []));
|
|
2046
|
+
return children.some(
|
|
672
2047
|
(child) => child.type.endsWith('Statement') || child.type === 'VariableDeclaration',
|
|
673
2048
|
);
|
|
674
2049
|
}
|
|
675
2050
|
|
|
676
2051
|
/**
|
|
677
|
-
* @param {
|
|
2052
|
+
* @param {ESTreeJSX.JSXElement | ESTreeJSX.JSXFragment} node
|
|
678
2053
|
*/
|
|
679
2054
|
#popTokenContextsAfterTemplateExpressionElement(node) {
|
|
2055
|
+
// A fragment in expression position (`() => <>…</>`) leaves the tokenizer
|
|
2056
|
+
// at `exprAllowed === false`, unlike a self-closing element. When the next
|
|
2057
|
+
// token is a `;`, the following statement may legitimately open with a JSX
|
|
2058
|
+
// tag (`<List/>`), so restore expression position to match the element path.
|
|
2059
|
+
if (this.type === tt.semi && node.type === 'JSXFragment') {
|
|
2060
|
+
this.exprAllowed = true;
|
|
2061
|
+
}
|
|
680
2062
|
const ctx = this.context;
|
|
681
2063
|
const ci = ctx.length - 1;
|
|
682
2064
|
const top = ctx[ci];
|
|
@@ -767,71 +2149,6 @@ export function TSRXPlugin(config) {
|
|
|
767
2149
|
}
|
|
768
2150
|
}
|
|
769
2151
|
|
|
770
|
-
#isDoubleQuotedTextChildStart() {
|
|
771
|
-
const current_template_node = this.#path.findLast(
|
|
772
|
-
(n) =>
|
|
773
|
-
n.type === 'Element' || n.type === 'Tsx' || n.type === 'Tsrx' || n.type === 'TsxCompat',
|
|
774
|
-
);
|
|
775
|
-
if (current_template_node?.type === 'TsxCompat' || current_template_node?.type === 'Tsx') {
|
|
776
|
-
return false;
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
const parent = this.#path.at(-1);
|
|
780
|
-
if (!parent || (parent.type !== 'Element' && parent.type !== 'Tsrx')) {
|
|
781
|
-
return false;
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
const context = this.curContext();
|
|
785
|
-
if (context === tstc.tc_oTag || context === tstc.tc_cTag) {
|
|
786
|
-
return false;
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
const prev = this.#previousNonWhitespaceChar();
|
|
790
|
-
return (
|
|
791
|
-
prev === null ||
|
|
792
|
-
prev === CharCode.doubleQuote ||
|
|
793
|
-
prev === CharCode.semicolon ||
|
|
794
|
-
prev === CharCode.greaterThan ||
|
|
795
|
-
(prev === CharCode.openBrace && this.#allowDoubleQuotedTextChildAfterBrace) ||
|
|
796
|
-
prev === CharCode.closeBrace
|
|
797
|
-
);
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
#readDoubleQuotedTextChildToken() {
|
|
801
|
-
const start = this.pos;
|
|
802
|
-
let out = '';
|
|
803
|
-
this.pos++;
|
|
804
|
-
let chunkStart = this.pos;
|
|
805
|
-
|
|
806
|
-
while (this.pos < this.input.length) {
|
|
807
|
-
const ch = this.input.charCodeAt(this.pos);
|
|
808
|
-
|
|
809
|
-
if (ch === CharCode.doubleQuote) {
|
|
810
|
-
out += this.input.slice(chunkStart, this.pos);
|
|
811
|
-
this.pos++;
|
|
812
|
-
return this.finishToken(tt.string, out);
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
if (ch === CharCode.ampersand) {
|
|
816
|
-
out += this.input.slice(chunkStart, this.pos);
|
|
817
|
-
out += this.jsx_readEntity();
|
|
818
|
-
chunkStart = this.pos;
|
|
819
|
-
continue;
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
if (acorn.isNewLine(ch)) {
|
|
823
|
-
out += this.input.slice(chunkStart, this.pos);
|
|
824
|
-
out += this.jsx_readNewLine(true);
|
|
825
|
-
chunkStart = this.pos;
|
|
826
|
-
continue;
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
this.pos++;
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
this.raise(start, 'Unterminated double-quoted text child');
|
|
833
|
-
}
|
|
834
|
-
|
|
835
2152
|
/**
|
|
836
2153
|
* @param {number} position
|
|
837
2154
|
* @param {number} end
|
|
@@ -922,8 +2239,9 @@ export function TSRXPlugin(config) {
|
|
|
922
2239
|
...node.metadata,
|
|
923
2240
|
invalid_tsrx_template_return: true,
|
|
924
2241
|
};
|
|
925
|
-
this.#
|
|
2242
|
+
this.#report_recoverable_error_range(
|
|
926
2243
|
/** @type {AST.NodeWithLocation} */ (node).start ?? this.start,
|
|
2244
|
+
/** @type {AST.NodeWithLocation} */ (node).end ?? this.start + 1,
|
|
927
2245
|
TSRX_RETURN_STATEMENT_ERROR,
|
|
928
2246
|
DIAGNOSTIC_CODES.TEMPLATE_RETURN_STATEMENT,
|
|
929
2247
|
);
|
|
@@ -1098,13 +2416,15 @@ export function TSRXPlugin(config) {
|
|
|
1098
2416
|
}
|
|
1099
2417
|
|
|
1100
2418
|
const container = this.#path[this.#path.length - 1];
|
|
1101
|
-
if (!container
|
|
2419
|
+
if (!this.#isNativeTemplateNode(container)) {
|
|
1102
2420
|
return null;
|
|
1103
2421
|
}
|
|
1104
2422
|
|
|
1105
|
-
const children = Array.isArray(
|
|
2423
|
+
const children = Array.isArray(/** @type {any} */ (container).children)
|
|
2424
|
+
? /** @type {any} */ (container).children
|
|
2425
|
+
: [];
|
|
1106
2426
|
const hasMeaningfulChildren = children.some(
|
|
1107
|
-
(child) => child && !isWhitespaceTextNode(child),
|
|
2427
|
+
(/** @type {any} */ child) => child && !isWhitespaceTextNode(child),
|
|
1108
2428
|
);
|
|
1109
2429
|
|
|
1110
2430
|
if (hasMeaningfulChildren) {
|
|
@@ -1149,9 +2469,58 @@ export function TSRXPlugin(config) {
|
|
|
1149
2469
|
* @type {Parse.Parser['readToken']}
|
|
1150
2470
|
*/
|
|
1151
2471
|
readToken(code) {
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
2472
|
+
const suppressTemplateRawTextToken = this.#suppressTemplateRawTextToken;
|
|
2473
|
+
this.#suppressTemplateRawTextToken = false;
|
|
2474
|
+
const context = this.curContext();
|
|
2475
|
+
if (
|
|
2476
|
+
code !== CharCode.lessThan &&
|
|
2477
|
+
code !== CharCode.greaterThan &&
|
|
2478
|
+
code !== CharCode.openBrace &&
|
|
2479
|
+
code !== CharCode.closeBrace &&
|
|
2480
|
+
!suppressTemplateRawTextToken &&
|
|
2481
|
+
this.#shouldReadTemplateRawTextToken()
|
|
2482
|
+
) {
|
|
2483
|
+
return this.#readTemplateRawTextToken();
|
|
2484
|
+
}
|
|
2485
|
+
if (
|
|
2486
|
+
code === CharCode.greaterThan &&
|
|
2487
|
+
this.input.charCodeAt(this.pos - 1) === CharCode.equals
|
|
2488
|
+
) {
|
|
2489
|
+
const start = this.pos - 1;
|
|
2490
|
+
const loc = acorn.getLineInfo(this.input, start);
|
|
2491
|
+
this.start = start;
|
|
2492
|
+
this.startLoc = loc;
|
|
2493
|
+
this.pos++;
|
|
2494
|
+
return this.finishToken(tt.arrow);
|
|
2495
|
+
}
|
|
2496
|
+
if (code === CharCode.lessThan) {
|
|
2497
|
+
const next = this.input.charCodeAt(this.pos + 1);
|
|
2498
|
+
if (
|
|
2499
|
+
next !== CharCode.slash &&
|
|
2500
|
+
(looks_like_generic_arrow(this.input, this.pos) ||
|
|
2501
|
+
this.#canStartTypeParameterOrArgumentList(this.pos))
|
|
2502
|
+
) {
|
|
2503
|
+
++this.pos;
|
|
2504
|
+
return this.finishToken(tt.relational, '<');
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
if (context === tstc.tc_expr || context === tstc.tc_oTag || context === tstc.tc_cTag) {
|
|
2508
|
+
return super.readToken(code);
|
|
2509
|
+
}
|
|
2510
|
+
if (code === CharCode.lessThan) {
|
|
2511
|
+
const next = this.input.charCodeAt(this.pos + 1);
|
|
2512
|
+
const isTagLikeAfterLt =
|
|
2513
|
+
next === CharCode.slash ||
|
|
2514
|
+
next === CharCode.greaterThan ||
|
|
2515
|
+
next === CharCode.at ||
|
|
2516
|
+
next === CharCode.dollar ||
|
|
2517
|
+
next === CharCode.underscore ||
|
|
2518
|
+
(next >= CharCode.uppercaseA && next <= CharCode.uppercaseZ) ||
|
|
2519
|
+
(next >= CharCode.lowercaseA && next <= CharCode.lowercaseZ);
|
|
2520
|
+
if (this.exprAllowed && isTagLikeAfterLt) {
|
|
2521
|
+
++this.pos;
|
|
2522
|
+
return this.finishToken(tstt.jsxTagStart);
|
|
2523
|
+
}
|
|
1155
2524
|
}
|
|
1156
2525
|
return super.readToken(code);
|
|
1157
2526
|
}
|
|
@@ -1161,74 +2530,91 @@ export function TSRXPlugin(config) {
|
|
|
1161
2530
|
* @type {Parse.Parser['getTokenFromCode']}
|
|
1162
2531
|
*/
|
|
1163
2532
|
getTokenFromCode(code) {
|
|
2533
|
+
// acorn-typescript only recognizes `@` as the at-token when it is not
|
|
2534
|
+
// reading a type. A return-type annotation (`function f(): T @{ … }`)
|
|
2535
|
+
// finishes while still `inType`, so its trailing `@` reaches the base
|
|
2536
|
+
// tokenizer, which throws "Unexpected character '@'". Emit the at-token
|
|
2537
|
+
// here so the `@{ … }` code block that follows the type can be parsed.
|
|
2538
|
+
if (code === CharCode.at && this.inType) {
|
|
2539
|
+
++this.pos;
|
|
2540
|
+
return this.finishToken(tstt.at);
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
if (
|
|
2544
|
+
code === CharCode.greaterThan &&
|
|
2545
|
+
this.input.charCodeAt(this.pos - 1) === CharCode.equals
|
|
2546
|
+
) {
|
|
2547
|
+
const start = this.pos - 1;
|
|
2548
|
+
const loc = acorn.getLineInfo(this.input, start);
|
|
2549
|
+
this.start = start;
|
|
2550
|
+
this.startLoc = loc;
|
|
2551
|
+
this.pos++;
|
|
2552
|
+
return this.finishToken(tt.arrow);
|
|
2553
|
+
}
|
|
2554
|
+
|
|
1164
2555
|
// Callback props that return native templates without a semicolon can
|
|
1165
2556
|
// leave the attribute expression context above the still-open tag. Drop
|
|
1166
2557
|
// it before tokenizing `/>`, otherwise Acorn treats `/` as a regexp.
|
|
1167
2558
|
if (
|
|
1168
2559
|
code === CharCode.slash &&
|
|
1169
|
-
this.input.charCodeAt(this.pos + 1) === CharCode.greaterThan
|
|
1170
|
-
this.context.includes(tstc.tc_oTag)
|
|
2560
|
+
this.input.charCodeAt(this.pos + 1) === CharCode.greaterThan
|
|
1171
2561
|
) {
|
|
1172
|
-
while (
|
|
2562
|
+
while (
|
|
2563
|
+
this.context.length > 0 &&
|
|
2564
|
+
this.curContext() !== tstc.tc_oTag &&
|
|
2565
|
+
this.curContext() !== tstc.tc_expr
|
|
2566
|
+
) {
|
|
1173
2567
|
this.context.pop();
|
|
1174
2568
|
}
|
|
1175
|
-
this.
|
|
1176
|
-
|
|
1177
|
-
if (code === CharCode.doubleQuote) {
|
|
1178
|
-
const is_double_quoted_text_child = this.#isDoubleQuotedTextChildStart();
|
|
1179
|
-
this.#allowDoubleQuotedTextChildAfterBrace = false;
|
|
1180
|
-
if (is_double_quoted_text_child) {
|
|
1181
|
-
return this.#readDoubleQuotedTextChildToken();
|
|
2569
|
+
if (this.curContext() !== tstc.tc_oTag) {
|
|
2570
|
+
this.context.push(tstc.tc_oTag);
|
|
1182
2571
|
}
|
|
1183
|
-
|
|
1184
|
-
this.#allowDoubleQuotedTextChildAfterBrace = false;
|
|
2572
|
+
this.exprAllowed = false;
|
|
1185
2573
|
}
|
|
1186
2574
|
|
|
1187
|
-
if (
|
|
1188
|
-
|
|
2575
|
+
if (
|
|
2576
|
+
(code === CharCode.numberSign || code === CharCode.slash) &&
|
|
2577
|
+
this.#functionBodyDepth === 0 &&
|
|
2578
|
+
this.#isNativeTemplateNode(this.#path.at(-1)) &&
|
|
2579
|
+
!(
|
|
2580
|
+
code === CharCode.slash &&
|
|
2581
|
+
(this.input.charCodeAt(this.pos - 1) === CharCode.lessThan ||
|
|
2582
|
+
this.input.charCodeAt(this.pos + 1) === CharCode.greaterThan)
|
|
2583
|
+
)
|
|
2584
|
+
) {
|
|
2585
|
+
++this.pos;
|
|
2586
|
+
return this.finishToken(tt.name, this.input.slice(this.start, this.pos));
|
|
1189
2587
|
}
|
|
1190
2588
|
|
|
1191
2589
|
if (code === CharCode.lessThan) {
|
|
1192
2590
|
// < character
|
|
1193
2591
|
const parent = this.#path.at(-1);
|
|
1194
2592
|
const inNativeTemplate =
|
|
1195
|
-
this.#functionBodyDepth === 0 &&
|
|
1196
|
-
(parent?.type === 'Element' || parent?.type === 'Tsrx');
|
|
2593
|
+
this.#functionBodyDepth === 0 && this.#isNativeTemplateNode(parent);
|
|
1197
2594
|
/** @type {number | null} */
|
|
1198
2595
|
let prevNonWhitespaceChar = null;
|
|
2596
|
+
const nextChar =
|
|
2597
|
+
this.pos + 1 < this.input.length ? this.input.charCodeAt(this.pos + 1) : -1;
|
|
1199
2598
|
|
|
1200
2599
|
// Check if this could be TypeScript generics instead of JSX
|
|
1201
|
-
// TypeScript generics appear
|
|
1202
|
-
//
|
|
2600
|
+
// TypeScript generics usually appear adjacent to an expression token,
|
|
2601
|
+
// for example: Array<T>, func<T>(), new Map<K,V>(), method<T>().
|
|
1203
2602
|
// This check applies everywhere, not just inside components
|
|
1204
2603
|
|
|
1205
2604
|
// Look back to see what precedes the <
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
// Skip whitespace backwards
|
|
1209
|
-
while (lookback >= 0) {
|
|
1210
|
-
const ch = this.input.charCodeAt(lookback);
|
|
1211
|
-
if (ch !== CharCode.space && ch !== CharCode.tab) break; // not space or tab
|
|
1212
|
-
lookback--;
|
|
1213
|
-
}
|
|
2605
|
+
const lookback = this.#previousNonSpaceTabIndex(this.pos);
|
|
1214
2606
|
|
|
1215
2607
|
// Check what character/token precedes the <
|
|
1216
2608
|
if (lookback >= 0) {
|
|
1217
2609
|
const prevChar = this.input.charCodeAt(lookback);
|
|
1218
2610
|
prevNonWhitespaceChar = prevChar;
|
|
1219
2611
|
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
prevChar === CharCode.underscore ||
|
|
1227
|
-
prevChar === CharCode.dollar ||
|
|
1228
|
-
prevChar === CharCode.closeParen;
|
|
1229
|
-
|
|
1230
|
-
if (isIdentifierChar) {
|
|
1231
|
-
return super.getTokenFromCode(code);
|
|
2612
|
+
if (
|
|
2613
|
+
nextChar !== CharCode.slash &&
|
|
2614
|
+
this.#canStartTypeParameterOrArgumentList(this.pos)
|
|
2615
|
+
) {
|
|
2616
|
+
++this.pos;
|
|
2617
|
+
return this.finishToken(tt.relational, '<');
|
|
1232
2618
|
}
|
|
1233
2619
|
}
|
|
1234
2620
|
|
|
@@ -1237,8 +2623,6 @@ export function TSRXPlugin(config) {
|
|
|
1237
2623
|
// <Something>...</Something>\n\n<Child />
|
|
1238
2624
|
// <head><style>...</style></head>
|
|
1239
2625
|
// We only do this when '<' is in a tag-like position.
|
|
1240
|
-
const nextChar =
|
|
1241
|
-
this.pos + 1 < this.input.length ? this.input.charCodeAt(this.pos + 1) : -1;
|
|
1242
2626
|
const isWhitespaceAfterLt =
|
|
1243
2627
|
nextChar === CharCode.space ||
|
|
1244
2628
|
nextChar === CharCode.tab ||
|
|
@@ -1261,6 +2645,11 @@ export function TSRXPlugin(config) {
|
|
|
1261
2645
|
prevNonWhitespaceChar === CharCode.closeBrace ||
|
|
1262
2646
|
prevNonWhitespaceChar === CharCode.greaterThan;
|
|
1263
2647
|
|
|
2648
|
+
if (!inNativeTemplate && this.exprAllowed && isTagLikeAfterLt) {
|
|
2649
|
+
++this.pos;
|
|
2650
|
+
return this.finishToken(tstt.jsxTagStart);
|
|
2651
|
+
}
|
|
2652
|
+
|
|
1264
2653
|
if (!inNativeTemplate && prevAllowsTagStart && isTagLikeAfterLt) {
|
|
1265
2654
|
++this.pos;
|
|
1266
2655
|
return this.finishToken(tstt.jsxTagStart);
|
|
@@ -1271,13 +2660,10 @@ export function TSRXPlugin(config) {
|
|
|
1271
2660
|
// a newline/indentation before the next '<'. This is important for inputs
|
|
1272
2661
|
// like `<div />` and `</div><style>...</style>` which Prettier formats.
|
|
1273
2662
|
if (
|
|
1274
|
-
(prevNonWhitespaceChar === CharCode.doubleQuote &&
|
|
1275
|
-
this.#allowTagStartAfterDoubleQuotedText) ||
|
|
1276
2663
|
prevNonWhitespaceChar === CharCode.openBrace ||
|
|
1277
2664
|
prevNonWhitespaceChar === CharCode.greaterThan
|
|
1278
2665
|
) {
|
|
1279
2666
|
if (!isWhitespaceAfterLt) {
|
|
1280
|
-
this.#allowTagStartAfterDoubleQuotedText = false;
|
|
1281
2667
|
++this.pos;
|
|
1282
2668
|
return this.finishToken(tstt.jsxTagStart);
|
|
1283
2669
|
}
|
|
@@ -1316,7 +2702,6 @@ export function TSRXPlugin(config) {
|
|
|
1316
2702
|
}
|
|
1317
2703
|
}
|
|
1318
2704
|
|
|
1319
|
-
this.#allowTagStartAfterDoubleQuotedText = false;
|
|
1320
2705
|
return super.getTokenFromCode(code);
|
|
1321
2706
|
}
|
|
1322
2707
|
|
|
@@ -1635,7 +3020,13 @@ export function TSRXPlugin(config) {
|
|
|
1635
3020
|
}
|
|
1636
3021
|
|
|
1637
3022
|
this.expect(tt.parenR);
|
|
1638
|
-
|
|
3023
|
+
const previous_reading_header = this.#readingJSXControlFlowHeader;
|
|
3024
|
+
this.#readingJSXControlFlowHeader = false;
|
|
3025
|
+
try {
|
|
3026
|
+
node.body = /** @type {AST.BlockStatement} */ (this.parseStatement('for'));
|
|
3027
|
+
} finally {
|
|
3028
|
+
this.#readingJSXControlFlowHeader = previous_reading_header;
|
|
3029
|
+
}
|
|
1639
3030
|
this.exitScope();
|
|
1640
3031
|
this.labels.pop();
|
|
1641
3032
|
return this.finishNode(node, isForIn ? 'ForInStatement' : 'ForOfStatement');
|
|
@@ -1647,6 +3038,25 @@ export function TSRXPlugin(config) {
|
|
|
1647
3038
|
parseFunctionBody(node, isArrowFunction, isMethod, forInit, ...args) {
|
|
1648
3039
|
this.#functionBodyDepth++;
|
|
1649
3040
|
try {
|
|
3041
|
+
// Allow a `@{ … }` code block as the body of a (non-arrow) function or
|
|
3042
|
+
// method, so a component can be written `function Something() @{ … }` or
|
|
3043
|
+
// `{ Render() @{ … } }`. Arrow concise bodies (`() => @{ … }`) already
|
|
3044
|
+
// route through `parseExprAtom`.
|
|
3045
|
+
//
|
|
3046
|
+
// A return-type annotation sits between the params and the body
|
|
3047
|
+
// (`function f(): T @{ … }`). acorn-typescript parses it inside
|
|
3048
|
+
// `super.parseFunctionBody` and then demands a `{` block, so the `@{ … }`
|
|
3049
|
+
// would never be seen. Parse the return type here first (exactly as
|
|
3050
|
+
// acorn-typescript does) so `this.start` lands on the `@` that follows.
|
|
3051
|
+
if (!isArrowFunction && this.match(tt.colon)) {
|
|
3052
|
+
node.returnType = this.tsParseTypeOrTypePredicateAnnotation(tt.colon);
|
|
3053
|
+
}
|
|
3054
|
+
if (!isArrowFunction && this.#isCodeBlockStart(this.start)) {
|
|
3055
|
+
node.body = this.parseMaybeAssign(forInit);
|
|
3056
|
+
this.checkParams(node, false);
|
|
3057
|
+
this.exitScope();
|
|
3058
|
+
return node;
|
|
3059
|
+
}
|
|
1650
3060
|
return super.parseFunctionBody(node, isArrowFunction, isMethod, forInit, ...args);
|
|
1651
3061
|
} finally {
|
|
1652
3062
|
this.#functionBodyDepth--;
|
|
@@ -1657,22 +3067,41 @@ export function TSRXPlugin(config) {
|
|
|
1657
3067
|
* @return {ESTreeJSX.JSXExpressionContainer}
|
|
1658
3068
|
*/
|
|
1659
3069
|
jsx_parseExpressionContainer() {
|
|
3070
|
+
// Template child containers consume `}` after leaving container scope, so
|
|
3071
|
+
// the following sibling — which may be raw template text — tokenizes
|
|
3072
|
+
// normally (acorn already preserves whitespace in the surrounding
|
|
3073
|
+
// `tc_expr` context). Attribute-value and script-mode JSX containers keep
|
|
3074
|
+
// consuming `}` in scope: their following token is part of the tag or JS,
|
|
3075
|
+
// never template text.
|
|
3076
|
+
const consumeBraceAfterScope = this.#consumeContainerBraceAfterScope;
|
|
3077
|
+
this.#consumeContainerBraceAfterScope = false;
|
|
1660
3078
|
let node = /** @type {ESTreeJSX.JSXExpressionContainer} */ (this.startNode());
|
|
1661
|
-
this
|
|
3079
|
+
this.#jsxExpressionContainerDepth++;
|
|
3080
|
+
try {
|
|
3081
|
+
this.next();
|
|
1662
3082
|
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
3083
|
+
node.expression =
|
|
3084
|
+
this.type === tt.braceR ? this.jsx_parseEmptyExpression() : this.parseExpression();
|
|
3085
|
+
if (this.#allowExpressionContainerTrailingSemicolon && this.type === tt.semi) {
|
|
3086
|
+
if (this.#collect) {
|
|
3087
|
+
this.#report_recoverable_error(
|
|
3088
|
+
this.start,
|
|
3089
|
+
'TSRX expression containers do not use semicolons. Remove this semicolon.',
|
|
3090
|
+
DIAGNOSTIC_CODES.TEMPLATE_EXPRESSION_TRAILING_SEMICOLON,
|
|
3091
|
+
);
|
|
3092
|
+
}
|
|
3093
|
+
this.next();
|
|
1672
3094
|
}
|
|
1673
|
-
|
|
3095
|
+
if (!consumeBraceAfterScope) {
|
|
3096
|
+
this.expect(tt.braceR);
|
|
3097
|
+
}
|
|
3098
|
+
} finally {
|
|
3099
|
+
this.#jsxExpressionContainerDepth--;
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
if (consumeBraceAfterScope) {
|
|
3103
|
+
this.expect(tt.braceR);
|
|
1674
3104
|
}
|
|
1675
|
-
this.expect(tt.braceR);
|
|
1676
3105
|
|
|
1677
3106
|
return this.finishNode(node, 'JSXExpressionContainer');
|
|
1678
3107
|
}
|
|
@@ -1705,88 +3134,140 @@ export function TSRXPlugin(config) {
|
|
|
1705
3134
|
);
|
|
1706
3135
|
}
|
|
1707
3136
|
|
|
1708
|
-
/**
|
|
1709
|
-
* @returns {AST.TextNode}
|
|
1710
|
-
*/
|
|
1711
|
-
parseDoubleQuotedTextChild() {
|
|
1712
|
-
const node = /** @type {AST.TextNode} */ (this.startNode());
|
|
1713
|
-
const expression = /** @type {AST.Literal} */ (this.startNode());
|
|
1714
|
-
node.raw = this.input.slice(this.start, this.end);
|
|
1715
|
-
const end = this.end;
|
|
1716
|
-
const endLoc = this.endLoc;
|
|
1717
|
-
|
|
1718
|
-
expression.value = this.value;
|
|
1719
|
-
expression.raw = JSON.stringify(this.value);
|
|
1720
|
-
node.expression = this.finishNodeAt(expression, 'Literal', end, endLoc);
|
|
1721
|
-
|
|
1722
|
-
this.#allowTagStartAfterDoubleQuotedText = true;
|
|
1723
|
-
try {
|
|
1724
|
-
this.next();
|
|
1725
|
-
} finally {
|
|
1726
|
-
this.#allowTagStartAfterDoubleQuotedText = false;
|
|
1727
|
-
}
|
|
1728
|
-
|
|
1729
|
-
return this.finishNodeAt(node, 'Text', end, endLoc);
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
3137
|
/**
|
|
1733
3138
|
* @type {Parse.Parser['jsx_parseAttribute']}
|
|
1734
3139
|
*/
|
|
1735
3140
|
jsx_parseAttribute() {
|
|
1736
|
-
let node =
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
);
|
|
3141
|
+
let node = /** @type {ESTreeJSX.JSXAttribute | ESTreeJSX.JSXSpreadAttribute} */ (
|
|
3142
|
+
this.startNode()
|
|
3143
|
+
);
|
|
1740
3144
|
|
|
1741
|
-
if (this.
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
n.type === 'Element' ||
|
|
1745
|
-
n.type === 'Tsx' ||
|
|
1746
|
-
n.type === 'Tsrx' ||
|
|
1747
|
-
n.type === 'TsxCompat',
|
|
1748
|
-
);
|
|
3145
|
+
if (this.type === tt.braceL) {
|
|
3146
|
+
let name_start = skip_whitespace_from(this.input, this.start + 1);
|
|
3147
|
+
const first = this.input.charCodeAt(name_start);
|
|
1749
3148
|
if (
|
|
1750
|
-
|
|
1751
|
-
|
|
3149
|
+
this.#isIdentifierChar(first) &&
|
|
3150
|
+
!(first >= CharCode.digit0 && first <= CharCode.digit9)
|
|
1752
3151
|
) {
|
|
3152
|
+
let name_end = name_start + 1;
|
|
3153
|
+
while (this.#isIdentifierChar(this.input.charCodeAt(name_end))) {
|
|
3154
|
+
name_end++;
|
|
3155
|
+
}
|
|
3156
|
+
const brace_start = skip_whitespace_from(this.input, name_end);
|
|
3157
|
+
if (this.input.charCodeAt(brace_start) === CharCode.closeBrace) {
|
|
3158
|
+
const name_start_loc = acorn.getLineInfo(this.input, name_start);
|
|
3159
|
+
const name_end_loc = acorn.getLineInfo(this.input, name_end);
|
|
3160
|
+
const name_value = this.input.slice(name_start, name_end);
|
|
3161
|
+
const id = /** @type {ESTreeJSX.JSXIdentifier} */ (
|
|
3162
|
+
this.startNodeAt(name_start, name_start_loc)
|
|
3163
|
+
);
|
|
3164
|
+
id.name = name_value;
|
|
3165
|
+
id.tracked = false;
|
|
3166
|
+
this.finishNodeAt(id, 'JSXIdentifier', name_end, name_end_loc);
|
|
3167
|
+
const name = /** @type {AST.Identifier} */ (
|
|
3168
|
+
this.startNodeAt(name_start, name_start_loc)
|
|
3169
|
+
);
|
|
3170
|
+
name.name = name_value;
|
|
3171
|
+
this.finishNodeAt(name, 'Identifier', name_end, name_end_loc);
|
|
3172
|
+
const expression = /** @type {ESTreeJSX.JSXExpressionContainer} */ (
|
|
3173
|
+
this.startNodeAt(this.start, this.startLoc)
|
|
3174
|
+
);
|
|
3175
|
+
expression.expression = name;
|
|
3176
|
+
this.finishNodeAt(
|
|
3177
|
+
expression,
|
|
3178
|
+
'JSXExpressionContainer',
|
|
3179
|
+
brace_start + 1,
|
|
3180
|
+
acorn.getLineInfo(this.input, brace_start + 1),
|
|
3181
|
+
);
|
|
3182
|
+
/** @type {ESTreeJSX.JSXAttribute} */ (node).name = id;
|
|
3183
|
+
/** @type {any} */ (node).value = expression;
|
|
3184
|
+
/** @type {any} */ (node).shorthand = true;
|
|
3185
|
+
|
|
3186
|
+
const end = brace_start + 1;
|
|
3187
|
+
const endLoc = acorn.getLineInfo(this.input, end);
|
|
3188
|
+
this.pos = end;
|
|
3189
|
+
this.curLine = endLoc.line;
|
|
3190
|
+
this.lineStart = end - endLoc.column;
|
|
3191
|
+
if (this.curContext()?.token === '{') {
|
|
3192
|
+
this.context.pop();
|
|
3193
|
+
}
|
|
3194
|
+
this.exprAllowed = false;
|
|
3195
|
+
this.next();
|
|
3196
|
+
return this.finishNodeAt(node, 'JSXAttribute', end, endLoc);
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
if (this.eat(tt.braceL)) {
|
|
3202
|
+
if (this.type === tt.ellipsis || this.input.slice(this.start, this.start + 3) === '...') {
|
|
3203
|
+
this.#suppressTemplateRawTextToken = true;
|
|
1753
3204
|
if (this.type === tt.ellipsis) {
|
|
1754
3205
|
this.expect(tt.ellipsis);
|
|
3206
|
+
} else {
|
|
3207
|
+
this.pos = this.start + 3;
|
|
3208
|
+
this.nextToken();
|
|
3209
|
+
}
|
|
3210
|
+
this.#templateScriptParsingDepth++;
|
|
3211
|
+
try {
|
|
1755
3212
|
/** @type {ESTreeJSX.JSXSpreadAttribute} */ (node).argument = this.parseMaybeAssign();
|
|
1756
|
-
|
|
1757
|
-
|
|
3213
|
+
} finally {
|
|
3214
|
+
this.#templateScriptParsingDepth--;
|
|
1758
3215
|
}
|
|
1759
|
-
this.unexpected();
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
if (this.type === tt.ellipsis) {
|
|
1763
|
-
this.expect(tt.ellipsis);
|
|
1764
|
-
/** @type {AST.SpreadAttribute} */ (node).argument = this.parseMaybeAssign();
|
|
1765
3216
|
this.expect(tt.braceR);
|
|
1766
|
-
return this.finishNode(node, '
|
|
3217
|
+
return this.finishNode(node, 'JSXSpreadAttribute');
|
|
1767
3218
|
} else if (this.lookahead().type === tt.ellipsis) {
|
|
3219
|
+
this.#suppressTemplateRawTextToken = true;
|
|
1768
3220
|
this.expect(tt.ellipsis);
|
|
1769
|
-
|
|
3221
|
+
this.#templateScriptParsingDepth++;
|
|
3222
|
+
try {
|
|
3223
|
+
/** @type {ESTreeJSX.JSXSpreadAttribute} */ (node).argument = this.parseMaybeAssign();
|
|
3224
|
+
} finally {
|
|
3225
|
+
this.#templateScriptParsingDepth--;
|
|
3226
|
+
}
|
|
1770
3227
|
this.expect(tt.braceR);
|
|
1771
|
-
return this.finishNode(node, '
|
|
3228
|
+
return this.finishNode(node, 'JSXSpreadAttribute');
|
|
1772
3229
|
} else {
|
|
1773
|
-
|
|
3230
|
+
if (!(this.type === tt.name || this.type.keyword || this.type === tstt.jsxName)) {
|
|
3231
|
+
this.unexpected();
|
|
3232
|
+
}
|
|
3233
|
+
const name_start = this.start;
|
|
3234
|
+
const name_start_loc = this.startLoc;
|
|
3235
|
+
const name_end = this.end;
|
|
3236
|
+
const name_end_loc = this.endLoc;
|
|
3237
|
+
const name_value = /** @type {string} */ (this.value);
|
|
3238
|
+
const id = /** @type {ESTreeJSX.JSXIdentifier} */ (
|
|
3239
|
+
this.startNodeAt(name_start, name_start_loc)
|
|
3240
|
+
);
|
|
3241
|
+
id.name = name_value;
|
|
1774
3242
|
id.tracked = false;
|
|
1775
|
-
this.
|
|
1776
|
-
/** @type {AST.
|
|
1777
|
-
|
|
1778
|
-
|
|
3243
|
+
this.finishNodeAt(id, 'JSXIdentifier', name_end, name_end_loc);
|
|
3244
|
+
const name = /** @type {AST.Identifier} */ (
|
|
3245
|
+
this.startNodeAt(name_start, name_start_loc)
|
|
3246
|
+
);
|
|
3247
|
+
name.name = name_value;
|
|
3248
|
+
this.finishNodeAt(name, 'Identifier', name_end, name_end_loc);
|
|
3249
|
+
const expression = /** @type {ESTreeJSX.JSXExpressionContainer} */ (
|
|
3250
|
+
this.startNodeAt(
|
|
3251
|
+
/** @type {number} */ (node.start),
|
|
3252
|
+
/** @type {AST.NodeWithLocation} */ (node).loc.start,
|
|
3253
|
+
)
|
|
3254
|
+
);
|
|
3255
|
+
expression.expression = name;
|
|
3256
|
+
/** @type {ESTreeJSX.JSXAttribute} */ (node).name = id;
|
|
3257
|
+
/** @type {any} */ (node).value = this.finishNodeAt(
|
|
3258
|
+
expression,
|
|
3259
|
+
'JSXExpressionContainer',
|
|
3260
|
+
this.end + 1,
|
|
3261
|
+
this.endLoc,
|
|
3262
|
+
);
|
|
3263
|
+
/** @type {any} */ (node).shorthand = true;
|
|
1779
3264
|
this.next();
|
|
1780
3265
|
this.expect(tt.braceR);
|
|
1781
|
-
return this.finishNode(node, '
|
|
3266
|
+
return this.finishNode(node, 'JSXAttribute');
|
|
1782
3267
|
}
|
|
1783
3268
|
}
|
|
1784
3269
|
/** @type {ESTreeJSX.JSXAttribute} */ (node).name = this.jsx_parseNamespacedName();
|
|
1785
|
-
if (
|
|
1786
|
-
/** @type {ESTreeJSX.JSXAttribute} */ (node).name.type === 'JSXIdentifier' &&
|
|
1787
|
-
/** @type {ESTreeJSX.JSXIdentifier} */ (/** @type {ESTreeJSX.JSXAttribute} */ (node).name)
|
|
1788
|
-
.tracked
|
|
1789
|
-
) {
|
|
3270
|
+
if (this.#isDynamicJSXElementName(/** @type {ESTreeJSX.JSXAttribute} */ (node).name)) {
|
|
1790
3271
|
this.#report_recoverable_error_range(
|
|
1791
3272
|
/** @type {AST.NodeWithLocation} */ (node).start,
|
|
1792
3273
|
/** @type {AST.NodeWithLocation} */ (/** @type {ESTreeJSX.JSXAttribute} */ (node).name)
|
|
@@ -1831,7 +3312,7 @@ export function TSRXPlugin(config) {
|
|
|
1831
3312
|
|
|
1832
3313
|
if (this.type === tt.name || this.type === tstt.jsxName) {
|
|
1833
3314
|
node.name = /** @type {string} */ (this.value);
|
|
1834
|
-
node.
|
|
3315
|
+
/** @type {any} */ (node).dynamic = true;
|
|
1835
3316
|
this.next();
|
|
1836
3317
|
} else {
|
|
1837
3318
|
// Unexpected token after @
|
|
@@ -1839,7 +3320,6 @@ export function TSRXPlugin(config) {
|
|
|
1839
3320
|
}
|
|
1840
3321
|
} else if (this.type === tt.name || this.type.keyword || this.type === tstt.jsxName) {
|
|
1841
3322
|
node.name = /** @type {string} */ (this.value);
|
|
1842
|
-
node.tracked = false; // Explicitly mark as not tracked
|
|
1843
3323
|
this.next();
|
|
1844
3324
|
} else {
|
|
1845
3325
|
return super.jsx_parseIdentifier();
|
|
@@ -1908,10 +3388,132 @@ export function TSRXPlugin(config) {
|
|
|
1908
3388
|
}
|
|
1909
3389
|
}
|
|
1910
3390
|
|
|
3391
|
+
/**
|
|
3392
|
+
* `@try`/`@pending`/`@catch`/`finally` blocks lower their direct `return`
|
|
3393
|
+
* values into reactive boundary fallbacks, so unlike `@if`/`@for`/`@switch`
|
|
3394
|
+
* blocks they legitimately allow `return <markup>` statements. Set the flag
|
|
3395
|
+
* immediately before parsing each such block so its body sees it.
|
|
3396
|
+
* @returns {AST.BlockStatement}
|
|
3397
|
+
*/
|
|
3398
|
+
#parseTemplateControlFlowReturnBlock(createNewLexicalScope = true) {
|
|
3399
|
+
this.#controlFlowBlockAllowsNativeReturn = true;
|
|
3400
|
+
return this.#parseTemplateControlFlowBlock(createNewLexicalScope);
|
|
3401
|
+
}
|
|
3402
|
+
|
|
1911
3403
|
/**
|
|
1912
3404
|
* @type {Parse.Parser['parseTryStatement']}
|
|
1913
3405
|
*/
|
|
1914
3406
|
parseTryStatement(node) {
|
|
3407
|
+
if (this.#templateControlFlowTryDepth > 0) {
|
|
3408
|
+
this.#templateControlFlowTryDepth--;
|
|
3409
|
+
try {
|
|
3410
|
+
this.next();
|
|
3411
|
+
node.block = this.#parseTemplateControlFlowReturnBlock();
|
|
3412
|
+
node.handler = null;
|
|
3413
|
+
|
|
3414
|
+
if (this.#eatJSXDirectiveClauseKeyword('pending')) {
|
|
3415
|
+
node.pending = this.#parseTemplateControlFlowReturnBlock();
|
|
3416
|
+
} else if (this.#isUnprefixedDirectiveClauseKeyword('pending')) {
|
|
3417
|
+
this.raise(this.start, 'Expected `@pending` after `@try` block.');
|
|
3418
|
+
} else {
|
|
3419
|
+
node.pending = null;
|
|
3420
|
+
}
|
|
3421
|
+
|
|
3422
|
+
const clauseStart = this.start;
|
|
3423
|
+
const clauseStartLoc = this.startLoc;
|
|
3424
|
+
if (this.#eatJSXDirectiveClauseKeyword('catch')) {
|
|
3425
|
+
if (this.type === tt._catch || this.value === 'catch') {
|
|
3426
|
+
this.next();
|
|
3427
|
+
}
|
|
3428
|
+
const paramStart = skip_whitespace_from(this.input, this.start);
|
|
3429
|
+
if (this.input.charCodeAt(paramStart) === CharCode.openParen) {
|
|
3430
|
+
this.pos = paramStart;
|
|
3431
|
+
this.start = paramStart;
|
|
3432
|
+
this.startLoc = acorn.getLineInfo(this.input, paramStart);
|
|
3433
|
+
this.curLine = this.startLoc.line;
|
|
3434
|
+
this.lineStart = paramStart - this.startLoc.column;
|
|
3435
|
+
this.context = this.context.filter(
|
|
3436
|
+
(context) =>
|
|
3437
|
+
context !== tstc.tc_expr &&
|
|
3438
|
+
context !== tstc.tc_oTag &&
|
|
3439
|
+
context !== tstc.tc_cTag,
|
|
3440
|
+
);
|
|
3441
|
+
if (this.curContext() !== b_stat) {
|
|
3442
|
+
this.context.push(b_stat);
|
|
3443
|
+
}
|
|
3444
|
+
this.exprAllowed = true;
|
|
3445
|
+
this.#suppressTemplateRawTextToken = true;
|
|
3446
|
+
try {
|
|
3447
|
+
this.nextToken();
|
|
3448
|
+
} finally {
|
|
3449
|
+
this.#suppressTemplateRawTextToken = false;
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
3452
|
+
const clause = /** @type {AST.CatchClause} */ (
|
|
3453
|
+
this.startNodeAt(clauseStart, clauseStartLoc)
|
|
3454
|
+
);
|
|
3455
|
+
const previous_reading_header = this.#readingJSXControlFlowHeader;
|
|
3456
|
+
this.#readingJSXControlFlowHeader = true;
|
|
3457
|
+
try {
|
|
3458
|
+
if (this.eat(tt.parenL)) {
|
|
3459
|
+
const param = this.parseBindingAtom();
|
|
3460
|
+
const simple = param.type === 'Identifier';
|
|
3461
|
+
this.enterScope(simple ? BINDING_TYPES.BIND_SIMPLE_CATCH : 0);
|
|
3462
|
+
this.checkLValPattern(
|
|
3463
|
+
param,
|
|
3464
|
+
simple ? BINDING_TYPES.BIND_SIMPLE_CATCH : BINDING_TYPES.BIND_LEXICAL,
|
|
3465
|
+
);
|
|
3466
|
+
const type = this.tsTryParseTypeAnnotation();
|
|
3467
|
+
if (type) {
|
|
3468
|
+
param.typeAnnotation = type;
|
|
3469
|
+
this.resetEndLocation(param);
|
|
3470
|
+
}
|
|
3471
|
+
clause.param = param;
|
|
3472
|
+
|
|
3473
|
+
if (this.eat(tt.comma)) {
|
|
3474
|
+
const reset_param = this.parseBindingAtom();
|
|
3475
|
+
this.checkLValSimple(reset_param, BINDING_TYPES.BIND_LEXICAL);
|
|
3476
|
+
const reset_type = this.tsTryParseTypeAnnotation();
|
|
3477
|
+
if (reset_type) {
|
|
3478
|
+
reset_param.typeAnnotation = reset_type;
|
|
3479
|
+
this.resetEndLocation(reset_param);
|
|
3480
|
+
}
|
|
3481
|
+
clause.resetParam = reset_param;
|
|
3482
|
+
} else {
|
|
3483
|
+
clause.resetParam = null;
|
|
3484
|
+
}
|
|
3485
|
+
|
|
3486
|
+
this.expect(tt.parenR);
|
|
3487
|
+
} else {
|
|
3488
|
+
clause.param = null;
|
|
3489
|
+
clause.resetParam = null;
|
|
3490
|
+
this.enterScope(0);
|
|
3491
|
+
}
|
|
3492
|
+
} finally {
|
|
3493
|
+
this.#readingJSXControlFlowHeader = previous_reading_header;
|
|
3494
|
+
}
|
|
3495
|
+
clause.body = this.#parseTemplateControlFlowReturnBlock(false);
|
|
3496
|
+
this.exitScope();
|
|
3497
|
+
node.handler = this.finishNode(clause, 'CatchClause');
|
|
3498
|
+
} else if (this.#isUnprefixedDirectiveClauseKeyword('catch')) {
|
|
3499
|
+
this.raise(this.start, 'Expected `@catch` after `@try` block.');
|
|
3500
|
+
}
|
|
3501
|
+
node.finalizer = this.eat(tt._finally)
|
|
3502
|
+
? this.#parseTemplateControlFlowReturnBlock()
|
|
3503
|
+
: null;
|
|
3504
|
+
|
|
3505
|
+
if (!node.handler && !node.finalizer && !node.pending) {
|
|
3506
|
+
this.raise(
|
|
3507
|
+
/** @type {AST.NodeWithLocation} */ (node).start,
|
|
3508
|
+
'Missing catch or finally clause',
|
|
3509
|
+
);
|
|
3510
|
+
}
|
|
3511
|
+
return this.finishNode(node, 'TryStatement');
|
|
3512
|
+
} finally {
|
|
3513
|
+
this.#templateControlFlowTryDepth++;
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
|
|
1915
3517
|
this.next();
|
|
1916
3518
|
node.block = this.parseBlock();
|
|
1917
3519
|
node.handler = null;
|
|
@@ -1980,25 +3582,53 @@ export function TSRXPlugin(config) {
|
|
|
1980
3582
|
|
|
1981
3583
|
/** @type {Parse.Parser['jsx_readToken']} */
|
|
1982
3584
|
jsx_readToken() {
|
|
1983
|
-
|
|
1984
|
-
(
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
3585
|
+
if (this.#scriptJSXElementDepth > 0 || this.#path.length === 0) {
|
|
3586
|
+
if (
|
|
3587
|
+
this.input.charCodeAt(this.pos) === CharCode.closeBrace &&
|
|
3588
|
+
this.context.includes(tstc.tc_expr)
|
|
3589
|
+
) {
|
|
3590
|
+
this.#resetTokenStartToCurrentPosition();
|
|
3591
|
+
return original.readToken.call(this, CharCode.closeBrace);
|
|
3592
|
+
}
|
|
3593
|
+
|
|
3594
|
+
let index = this.pos;
|
|
3595
|
+
while (
|
|
3596
|
+
this.input.charCodeAt(index) === CharCode.space ||
|
|
3597
|
+
this.input.charCodeAt(index) === CharCode.tab ||
|
|
3598
|
+
this.input.charCodeAt(index) === CharCode.lineFeed ||
|
|
3599
|
+
this.input.charCodeAt(index) === CharCode.carriageReturn
|
|
3600
|
+
) {
|
|
3601
|
+
index++;
|
|
3602
|
+
}
|
|
3603
|
+
if (
|
|
3604
|
+
index !== this.pos &&
|
|
3605
|
+
this.input.charCodeAt(index) === CharCode.slash &&
|
|
3606
|
+
this.input.charCodeAt(index + 1) === CharCode.greaterThan &&
|
|
3607
|
+
this.context.includes(tstc.tc_expr)
|
|
3608
|
+
) {
|
|
3609
|
+
const loc = acorn.getLineInfo(this.input, index);
|
|
3610
|
+
this.pos = index;
|
|
3611
|
+
this.start = index;
|
|
3612
|
+
this.startLoc = loc;
|
|
3613
|
+
this.curLine = loc.line;
|
|
3614
|
+
this.lineStart = index - loc.column;
|
|
3615
|
+
this.exprAllowed = false;
|
|
3616
|
+
if (this.curContext() !== tstc.tc_oTag) {
|
|
3617
|
+
this.context.push(tstc.tc_oTag);
|
|
3618
|
+
}
|
|
3619
|
+
return original.readToken.call(this, CharCode.slash);
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
if (this.#scriptJSXElementDepth > 0 || this.#path.length === 0) {
|
|
1988
3623
|
return super.jsx_readToken();
|
|
1989
3624
|
}
|
|
3625
|
+
|
|
1990
3626
|
let out = '',
|
|
1991
3627
|
chunkStart = this.pos;
|
|
1992
3628
|
|
|
1993
3629
|
while (true) {
|
|
1994
3630
|
if (this.pos >= this.input.length) {
|
|
1995
|
-
const inside_open_template = this.#path.findLast(
|
|
1996
|
-
(n) =>
|
|
1997
|
-
n.type === 'Element' ||
|
|
1998
|
-
n.type === 'Tsrx' ||
|
|
1999
|
-
n.type === 'TsxCompat' ||
|
|
2000
|
-
n.type === 'Tsx',
|
|
2001
|
-
);
|
|
3631
|
+
const inside_open_template = this.#path.findLast((n) => this.#isNativeTemplateNode(n));
|
|
2002
3632
|
if (!inside_open_template) {
|
|
2003
3633
|
while (this.curContext() === tstc.tc_expr) {
|
|
2004
3634
|
this.context.pop();
|
|
@@ -2009,9 +3639,30 @@ export function TSRXPlugin(config) {
|
|
|
2009
3639
|
}
|
|
2010
3640
|
let ch = this.input.charCodeAt(this.pos);
|
|
2011
3641
|
|
|
2012
|
-
switch (ch) {
|
|
3642
|
+
switch (ch) {
|
|
3643
|
+
case CharCode.equals:
|
|
3644
|
+
if (
|
|
3645
|
+
!this.#shouldReadTemplateRawTextToken() &&
|
|
3646
|
+
this.input.charCodeAt(this.pos + 1) === CharCode.greaterThan
|
|
3647
|
+
) {
|
|
3648
|
+
this.#resetTokenStartToCurrentPosition();
|
|
3649
|
+
this.pos += 2;
|
|
3650
|
+
return this.finishToken(tt.arrow);
|
|
3651
|
+
}
|
|
3652
|
+
if (this.#shouldReadTemplateRawTextToken()) {
|
|
3653
|
+
++this.pos;
|
|
3654
|
+
break;
|
|
3655
|
+
}
|
|
3656
|
+
this.#resetTokenStartToCurrentPosition();
|
|
3657
|
+
this.context.push(b_stat);
|
|
3658
|
+
this.exprAllowed = true;
|
|
3659
|
+
return original.readToken.call(this, ch);
|
|
3660
|
+
|
|
2013
3661
|
case CharCode.lessThan:
|
|
2014
3662
|
case CharCode.openBrace:
|
|
3663
|
+
if (out || this.pos > chunkStart) {
|
|
3664
|
+
return this.finishToken(tstt.jsxText, out + this.input.slice(chunkStart, this.pos));
|
|
3665
|
+
}
|
|
2015
3666
|
// In JSX text mode, '<' and '{' always start a tag/expression container.
|
|
2016
3667
|
// `exprAllowed` can be false here due to surrounding parser state, but
|
|
2017
3668
|
// throwing breaks valid templates (e.g. sibling tags after a close).
|
|
@@ -2058,6 +3709,7 @@ export function TSRXPlugin(config) {
|
|
|
2058
3709
|
}
|
|
2059
3710
|
|
|
2060
3711
|
// Continue processing from current position
|
|
3712
|
+
chunkStart = this.pos;
|
|
2061
3713
|
break;
|
|
2062
3714
|
} else if (this.input.charCodeAt(this.pos + 1) === CharCode.asterisk) {
|
|
2063
3715
|
// '/*'
|
|
@@ -2097,9 +3749,13 @@ export function TSRXPlugin(config) {
|
|
|
2097
3749
|
}
|
|
2098
3750
|
|
|
2099
3751
|
// Continue processing from current position
|
|
3752
|
+
chunkStart = this.pos;
|
|
3753
|
+
break;
|
|
3754
|
+
}
|
|
3755
|
+
if (this.#shouldReadTemplateRawTextToken()) {
|
|
3756
|
+
++this.pos;
|
|
2100
3757
|
break;
|
|
2101
3758
|
}
|
|
2102
|
-
// If not a comment, fall through to default case
|
|
2103
3759
|
this.#resetTokenStartToCurrentPosition();
|
|
2104
3760
|
this.context.push(b_stat);
|
|
2105
3761
|
this.exprAllowed = true;
|
|
@@ -2114,10 +3770,21 @@ export function TSRXPlugin(config) {
|
|
|
2114
3770
|
case CharCode.greaterThan:
|
|
2115
3771
|
case CharCode.closeBrace: {
|
|
2116
3772
|
if (
|
|
2117
|
-
ch === CharCode.
|
|
2118
|
-
(this
|
|
2119
|
-
|
|
2120
|
-
|
|
3773
|
+
ch === CharCode.greaterThan &&
|
|
3774
|
+
this.input.charCodeAt(this.pos - 1) === CharCode.equals &&
|
|
3775
|
+
!this.#shouldReadTemplateRawTextToken()
|
|
3776
|
+
) {
|
|
3777
|
+
const start = this.pos - 1;
|
|
3778
|
+
const loc = acorn.getLineInfo(this.input, start);
|
|
3779
|
+
this.start = start;
|
|
3780
|
+
this.startLoc = loc;
|
|
3781
|
+
this.pos++;
|
|
3782
|
+
return this.finishToken(tt.arrow);
|
|
3783
|
+
}
|
|
3784
|
+
if (
|
|
3785
|
+
this.#isInsideNativeTemplateScriptSection() ||
|
|
3786
|
+
(ch === CharCode.closeBrace &&
|
|
3787
|
+
(this.#path.length === 0 || this.#isNativeTemplateNode(this.#path.at(-1))))
|
|
2121
3788
|
) {
|
|
2122
3789
|
this.#resetTokenStartToCurrentPosition();
|
|
2123
3790
|
return original.readToken.call(this, ch);
|
|
@@ -2144,6 +3811,10 @@ export function TSRXPlugin(config) {
|
|
|
2144
3811
|
} else if (ch === CharCode.space || ch === CharCode.tab) {
|
|
2145
3812
|
++this.pos;
|
|
2146
3813
|
} else {
|
|
3814
|
+
if (this.#shouldReadTemplateRawTextToken()) {
|
|
3815
|
+
++this.pos;
|
|
3816
|
+
break;
|
|
3817
|
+
}
|
|
2147
3818
|
this.#resetTokenStartToCurrentPosition();
|
|
2148
3819
|
this.context.push(b_stat);
|
|
2149
3820
|
this.exprAllowed = true;
|
|
@@ -2154,523 +3825,326 @@ export function TSRXPlugin(config) {
|
|
|
2154
3825
|
}
|
|
2155
3826
|
|
|
2156
3827
|
/**
|
|
2157
|
-
* Override jsx_parseElement to
|
|
2158
|
-
*
|
|
2159
|
-
* for their children.
|
|
3828
|
+
* Override jsx_parseElement to use TSRX template parsing only where the
|
|
3829
|
+
* fragment/element body can contain TSRX-only syntax.
|
|
2160
3830
|
* @type {Parse.Parser['jsx_parseElement']}
|
|
2161
3831
|
*/
|
|
2162
3832
|
jsx_parseElement() {
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
);
|
|
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,
|
|
3833
|
+
if (this.#forceScriptJSXElementDepth > 0 || this.#isInsideNativeTemplateScriptSection()) {
|
|
3834
|
+
if (this.#isStyleOpeningTagStart()) {
|
|
3835
|
+
this.next();
|
|
3836
|
+
return /** @type {ESTreeJSX.JSXElement | AST.JSXStyleElement} */ (
|
|
3837
|
+
/** @type {unknown} */ (this.parseElement())
|
|
2177
3838
|
);
|
|
2178
3839
|
}
|
|
2179
|
-
|
|
2180
|
-
|
|
3840
|
+
|
|
3841
|
+
this.#scriptJSXElementDepth++;
|
|
3842
|
+
try {
|
|
3843
|
+
return super.jsx_parseElement();
|
|
3844
|
+
} finally {
|
|
3845
|
+
this.#scriptJSXElementDepth--;
|
|
3846
|
+
}
|
|
2181
3847
|
}
|
|
2182
3848
|
|
|
2183
3849
|
this.next();
|
|
2184
3850
|
const parsed = /** @type {import('estree-jsx').JSXElement} */ (
|
|
2185
3851
|
/** @type {unknown} */ (this.parseElement())
|
|
2186
3852
|
);
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
3853
|
+
this.#popTokenContextsAfterTemplateExpressionElement(parsed);
|
|
3854
|
+
return parsed;
|
|
3855
|
+
}
|
|
3856
|
+
|
|
3857
|
+
/**
|
|
3858
|
+
* @type {Parse.Parser['jsx_parseElementAt']}
|
|
3859
|
+
*/
|
|
3860
|
+
jsx_parseElementAt(startPos, startLoc) {
|
|
3861
|
+
if (this.input.charCodeAt(startPos + 1) === CharCode.at) {
|
|
3862
|
+
const previous_script_jsx_element_depth = this.#scriptJSXElementDepth;
|
|
3863
|
+
this.#scriptJSXElementDepth = 0;
|
|
3864
|
+
try {
|
|
3865
|
+
const parsed = /** @type {ESTreeJSX.JSXElement} */ (
|
|
3866
|
+
/** @type {unknown} */ (this.parseElement())
|
|
3867
|
+
);
|
|
3868
|
+
// A dynamic `<@tag>` parsed here goes straight through `parseElement`,
|
|
3869
|
+
// bypassing `jsx_parseElement`'s context cleanup. In expression
|
|
3870
|
+
// position (e.g. a render-prop arrow body inside object params) its
|
|
3871
|
+
// markup contexts must be unwound, or the following JS token (a `,`/`}`)
|
|
3872
|
+
// is mis-tokenized as JSX raw text.
|
|
3873
|
+
this.#popTokenContextsAfterTemplateExpressionElement(parsed);
|
|
3874
|
+
return parsed;
|
|
3875
|
+
} finally {
|
|
3876
|
+
this.#scriptJSXElementDepth = previous_script_jsx_element_depth;
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
|
|
3880
|
+
return super.jsx_parseElementAt(startPos, startLoc);
|
|
3881
|
+
}
|
|
3882
|
+
|
|
3883
|
+
/**
|
|
3884
|
+
* @type {Parse.Parser['jsx_parseOpeningElementAt']}
|
|
3885
|
+
*/
|
|
3886
|
+
jsx_parseOpeningElementAt(startPos, startLoc) {
|
|
3887
|
+
const node = /** @type {ESTreeJSX.JSXOpeningElement & AST.NodeWithLocation} */ (
|
|
3888
|
+
this.startNodeAt(/** @type {number} */ (startPos), /** @type {AST.Position} */ (startLoc))
|
|
3889
|
+
);
|
|
3890
|
+
node.attributes = [];
|
|
3891
|
+
const nodeName = this.jsx_parseElementName();
|
|
3892
|
+
if (nodeName) node.name = nodeName;
|
|
3893
|
+
if (this.#isDynamicJSXElementName(nodeName)) {
|
|
3894
|
+
/** @type {any} */ (node).dynamic = true;
|
|
3895
|
+
}
|
|
3896
|
+
if (this.match(tt.relational) || this.match(tt.bitShift)) {
|
|
3897
|
+
const typeArguments = /** @type {any} */ (this).tsTryParseAndCatch(() =>
|
|
3898
|
+
/** @type {any} */ (this).tsParseTypeArgumentsInExpression(),
|
|
2190
3899
|
);
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
3900
|
+
if (typeArguments) node.typeArguments = typeArguments;
|
|
3901
|
+
}
|
|
3902
|
+
while (this.type !== tt.slash && this.type !== tstt.jsxTagEnd) {
|
|
3903
|
+
node.attributes.push(this.jsx_parseAttribute());
|
|
3904
|
+
}
|
|
3905
|
+
node.selfClosing = this.eat(tt.slash);
|
|
3906
|
+
|
|
3907
|
+
const opening_template_node = this.#openingNativeTemplateNode;
|
|
3908
|
+
let pushed_opening_template_node = false;
|
|
3909
|
+
if (opening_template_node) {
|
|
3910
|
+
if (nodeName) {
|
|
3911
|
+
/** @type {any} */ (opening_template_node).type =
|
|
3912
|
+
this.getElementName(nodeName) === 'style' ? 'JSXStyleElement' : 'JSXElement';
|
|
3913
|
+
/** @type {any} */ (opening_template_node).openingElement = node;
|
|
3914
|
+
/** @type {any} */ (opening_template_node).closingElement = null;
|
|
3915
|
+
} else {
|
|
3916
|
+
/** @type {any} */ (opening_template_node).type = 'JSXFragment';
|
|
3917
|
+
/** @type {any} */ (opening_template_node).openingFragment =
|
|
3918
|
+
this.#toOpeningFragment(node);
|
|
3919
|
+
/** @type {any} */ (opening_template_node).closingFragment = null;
|
|
2197
3920
|
}
|
|
3921
|
+
this.#path.push(opening_template_node);
|
|
3922
|
+
pushed_opening_template_node = true;
|
|
2198
3923
|
}
|
|
2199
|
-
|
|
3924
|
+
|
|
3925
|
+
try {
|
|
3926
|
+
this.expect(tstt.jsxTagEnd);
|
|
3927
|
+
} finally {
|
|
3928
|
+
if (pushed_opening_template_node) {
|
|
3929
|
+
this.#path.pop();
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
if (nodeName) {
|
|
3933
|
+
return this.finishNode(node, 'JSXOpeningElement');
|
|
3934
|
+
}
|
|
3935
|
+
return /** @type {any} */ (
|
|
3936
|
+
/** @type {any} */ (this).finishNode(node, 'JSXOpeningFragment')
|
|
3937
|
+
);
|
|
2200
3938
|
}
|
|
2201
3939
|
|
|
2202
3940
|
/**
|
|
2203
3941
|
* @type {Parse.Parser['parseElement']}
|
|
2204
3942
|
*/
|
|
2205
3943
|
parseElement() {
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
)
|
|
3944
|
+
// Depth the tokenizer context must return to once this element closes:
|
|
3945
|
+
// the stack with the element's own opening `<` contexts (a trailing
|
|
3946
|
+
// tc_oTag/tc_expr) stripped off. A balanced element should leave the
|
|
3947
|
+
// stack here; the body (especially a control-flow block) can otherwise
|
|
3948
|
+
// leave residue that breaks tokenizing the following JS token when the
|
|
3949
|
+
// element is in expression position.
|
|
3950
|
+
let pre_element_context_depth = this.context.length;
|
|
3951
|
+
while (pre_element_context_depth > 0) {
|
|
3952
|
+
const ctx = this.context[pre_element_context_depth - 1];
|
|
3953
|
+
if (ctx === tstc.tc_expr || ctx === tstc.tc_oTag || ctx === tstc.tc_cTag) {
|
|
3954
|
+
pre_element_context_depth--;
|
|
3955
|
+
} else {
|
|
3956
|
+
break;
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
|
|
2209
3960
|
// Adjust the start so we capture the `<` as part of the element
|
|
2210
3961
|
const start = this.start - 1;
|
|
2211
3962
|
const position = new acorn.Position(this.curLine, start - this.lineStart);
|
|
2212
3963
|
|
|
2213
|
-
const
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
3964
|
+
const node =
|
|
3965
|
+
/** @type {ESTreeJSX.JSXElement | ESTreeJSX.JSXFragment | AST.JSXStyleElement} */ (
|
|
3966
|
+
/** @type {unknown} */ (this.startNode())
|
|
3967
|
+
);
|
|
3968
|
+
node.start = start;
|
|
3969
|
+
/** @type {AST.NodeWithLocation} */ (node).loc.start = position;
|
|
3970
|
+
node.metadata = {
|
|
3971
|
+
path: [],
|
|
3972
|
+
native_tsrx: true,
|
|
3973
|
+
templateMode: 'script',
|
|
3974
|
+
};
|
|
3975
|
+
node.children = [];
|
|
3976
|
+
|
|
3977
|
+
const previous_opening_native_template_node = this.#openingNativeTemplateNode;
|
|
3978
|
+
this.#openingNativeTemplateNode = node;
|
|
3979
|
+
let open;
|
|
3980
|
+
try {
|
|
3981
|
+
open = /** @type {ESTreeJSX.JSXOpeningElement & AST.NodeWithLocation} */ (
|
|
3982
|
+
this.jsx_parseOpeningElementAt(start, position)
|
|
3983
|
+
);
|
|
3984
|
+
} finally {
|
|
3985
|
+
this.#openingNativeTemplateNode = previous_opening_native_template_node;
|
|
3986
|
+
}
|
|
3987
|
+
const tag_name = open.name ? this.getElementName(open.name) : null;
|
|
3988
|
+
const is_style = tag_name === 'style';
|
|
3989
|
+
const inside_head = this.#path.findLast((n) => this.#isNativeElementNamed(n, 'head'));
|
|
2229
3990
|
|
|
2230
3991
|
// Fragments (<>) produce JSXOpeningFragment with no `name` property
|
|
2231
3992
|
const is_fragment = !open.name;
|
|
2232
|
-
const
|
|
2233
|
-
const
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
const is_dynamic_name =
|
|
2239
|
-
!is_fragment &&
|
|
2240
|
-
((open.name.type === 'JSXIdentifier' && open.name.tracked) ||
|
|
2241
|
-
(open.name.type === 'JSXMemberExpression' &&
|
|
2242
|
-
open.name.object.type === 'JSXIdentifier' &&
|
|
2243
|
-
open.name.object.tracked));
|
|
2244
|
-
|
|
2245
|
-
if (is_tsx_compat) {
|
|
3993
|
+
const parent_template_node = this.#currentNativeTemplateNode();
|
|
3994
|
+
const parent_is_template_output =
|
|
3995
|
+
parent_template_node?.metadata?.templateMode === 'template';
|
|
3996
|
+
node.metadata.templateMode =
|
|
3997
|
+
is_fragment && parent_is_template_output ? 'template' : 'script';
|
|
3998
|
+
if (!is_fragment && open.name.type === 'JSXNamespacedName') {
|
|
2246
3999
|
const namespace_node = /** @type {ESTreeJSX.JSXNamespacedName} */ (open.name);
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
const tagName = namespace_node.namespace.name + ':' + namespace_node.name.name;
|
|
2252
|
-
this.raise(
|
|
2253
|
-
open.start,
|
|
2254
|
-
`TSX compatibility elements cannot be self-closing. '<${tagName} />' must have a closing tag '</${tagName}>'.`,
|
|
2255
|
-
);
|
|
2256
|
-
}
|
|
2257
|
-
} else if (is_tsx) {
|
|
2258
|
-
/** @type {AST.Tsx} */ (element).type = 'Tsx';
|
|
2259
|
-
|
|
2260
|
-
if (open.selfClosing) {
|
|
2261
|
-
this.raise(
|
|
2262
|
-
open.start,
|
|
2263
|
-
`TSX elements cannot be self-closing. '<tsx />' must have a closing tag '</tsx>'.`,
|
|
2264
|
-
);
|
|
2265
|
-
}
|
|
2266
|
-
} else if (is_fragment) {
|
|
2267
|
-
/** @type {AST.Tsrx} */ (element).type = 'Tsrx';
|
|
2268
|
-
} else {
|
|
2269
|
-
element.type = 'Element';
|
|
2270
|
-
}
|
|
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,
|
|
4000
|
+
const tagName = namespace_node.namespace.name + ':' + namespace_node.name.name;
|
|
4001
|
+
this.raise(
|
|
4002
|
+
open.start,
|
|
4003
|
+
`Namespaced elements are not supported in TSRX templates: <${tagName}>.`,
|
|
2277
4004
|
);
|
|
2278
4005
|
}
|
|
2279
4006
|
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
}
|
|
4007
|
+
if (is_fragment) {
|
|
4008
|
+
/** @type {ESTreeJSX.JSXFragment} */ (node).type = 'JSXFragment';
|
|
4009
|
+
/** @type {ESTreeJSX.JSXFragment} */ (node).openingFragment =
|
|
4010
|
+
this.#toOpeningFragment(open);
|
|
4011
|
+
/** @type {any} */ (node).closingFragment = null;
|
|
4012
|
+
} else {
|
|
4013
|
+
if (is_style) {
|
|
4014
|
+
/** @type {AST.JSXStyleElement} */ (node).type = 'JSXStyleElement';
|
|
4015
|
+
/** @type {AST.JSXStyleElement} */ (node).openingElement = open;
|
|
4016
|
+
/** @type {AST.JSXStyleElement} */ (node).closingElement = null;
|
|
4017
|
+
} else {
|
|
4018
|
+
/** @type {ESTreeJSX.JSXElement} */ (node).type = 'JSXElement';
|
|
4019
|
+
/** @type {ESTreeJSX.JSXElement} */ (node).openingElement = open;
|
|
4020
|
+
/** @type {ESTreeJSX.JSXElement} */ (node).closingElement = null;
|
|
4021
|
+
if (/** @type {any} */ (open).dynamic) {
|
|
4022
|
+
/** @type {any} */ (node).dynamic = true;
|
|
2297
4023
|
}
|
|
2298
4024
|
}
|
|
2299
4025
|
}
|
|
2300
4026
|
|
|
2301
|
-
if (!is_tsx_compat && !is_tsx && !is_fragment) {
|
|
2302
|
-
/** @type {AST.Element} */ (element).id = /** @type {AST.Identifier} */ (
|
|
2303
|
-
convert_from_jsx(/** @type {ESTreeJSX.JSXIdentifier} */ (open.name))
|
|
2304
|
-
);
|
|
2305
|
-
element.selfClosing = open.selfClosing;
|
|
2306
|
-
} else if (is_fragment) {
|
|
2307
|
-
element.selfClosing = false;
|
|
2308
|
-
}
|
|
2309
|
-
|
|
2310
|
-
element.attributes = open.attributes;
|
|
2311
|
-
element.metadata ??= { path: [] };
|
|
2312
4027
|
// Opening-tag parsing can tokenize comments that appear before the first
|
|
2313
4028
|
// child. Preserve that early container id so the comment stays associated
|
|
2314
4029
|
// with this element during comment attachment/printing.
|
|
2315
|
-
if (
|
|
2316
|
-
|
|
4030
|
+
if (node.metadata.commentContainerId === undefined) {
|
|
4031
|
+
node.metadata.commentContainerId = ++this.#commentContextId;
|
|
2317
4032
|
}
|
|
2318
4033
|
|
|
2319
|
-
|
|
2320
|
-
this.#path.pop();
|
|
4034
|
+
this.#path.push(node);
|
|
2321
4035
|
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
}
|
|
2326
|
-
|
|
2327
|
-
|
|
4036
|
+
if (!is_fragment && open.selfClosing) {
|
|
4037
|
+
this.#path.pop();
|
|
4038
|
+
} else if (is_style) {
|
|
4039
|
+
this.#parseStyleElement(open, /** @type {AST.JSXStyleElement} */ (node), !!inside_head);
|
|
4040
|
+
this.#path.pop();
|
|
4041
|
+
} else {
|
|
4042
|
+
this.#parseNativeTemplateBody(node, /** @type {AST.Node[]} */ (node.children), {
|
|
2328
4043
|
enterScope: true,
|
|
2329
4044
|
resetFunctionBodyDepth: true,
|
|
2330
4045
|
});
|
|
2331
4046
|
|
|
2332
|
-
this.#path.
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
4047
|
+
if (this.#path[this.#path.length - 1] === node) {
|
|
4048
|
+
const displayTag = is_fragment
|
|
4049
|
+
? ''
|
|
4050
|
+
: this.getElementName(/** @type {ESTreeJSX.JSXElement} */ (node).openingElement.name);
|
|
4051
|
+
this.#report_broken_markup_error(
|
|
4052
|
+
this.start,
|
|
4053
|
+
`Unclosed tag '<${displayTag}>'. Expected '</${displayTag}>' before end of template.`,
|
|
4054
|
+
);
|
|
4055
|
+
/** @type {any} */ (node).unclosed = true;
|
|
4056
|
+
/** @type {AST.SourceLocation} */ (node.loc).end = {
|
|
4057
|
+
.../** @type {AST.SourceLocation} */ (
|
|
4058
|
+
is_fragment
|
|
4059
|
+
? /** @type {ESTreeJSX.JSXFragment} */ (node).openingFragment.loc
|
|
4060
|
+
: /** @type {ESTreeJSX.JSXElement} */ (node).openingElement.loc
|
|
4061
|
+
).end,
|
|
2337
4062
|
};
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
}
|
|
2343
|
-
this.next();
|
|
2344
|
-
if (this.type !== tstt.jsxTagEnd) {
|
|
2345
|
-
raise_error();
|
|
2346
|
-
}
|
|
2347
|
-
this.#popTsxTokenContextBeforeTemplateExpressionChild();
|
|
2348
|
-
this.next();
|
|
2349
|
-
}
|
|
2350
|
-
} else {
|
|
2351
|
-
if (/** @type {ESTreeJSX.JSXIdentifier} */ (open.name).name === 'script') {
|
|
2352
|
-
let content = '';
|
|
2353
|
-
|
|
2354
|
-
// TODO implement this where we get a string for content of the content of the script tag
|
|
2355
|
-
// This is a temporary workaround to get the content of the script tag
|
|
2356
|
-
const start = open.end;
|
|
2357
|
-
const input = this.input.slice(start);
|
|
2358
|
-
const end = input.indexOf('</script>');
|
|
2359
|
-
content = end === -1 ? input : input.slice(0, end);
|
|
2360
|
-
|
|
2361
|
-
const newLines = content.match(regex_newline_characters)?.length;
|
|
2362
|
-
if (newLines) {
|
|
2363
|
-
this.curLine = open.loc.end.line + newLines;
|
|
2364
|
-
this.lineStart = start + content.lastIndexOf('\n') + 1;
|
|
2365
|
-
}
|
|
2366
|
-
if (end !== -1) {
|
|
2367
|
-
const closingStart = start + content.length;
|
|
2368
|
-
const closingLineInfo = acorn.getLineInfo(this.input, closingStart);
|
|
2369
|
-
const closingStartLoc = new acorn.Position(
|
|
2370
|
-
closingLineInfo.line,
|
|
2371
|
-
closingLineInfo.column,
|
|
2372
|
-
);
|
|
2373
|
-
|
|
2374
|
-
// Ensure `</script>` can't be tokenized as `<` followed by a regexp
|
|
2375
|
-
// start when we manually advance to the `/`.
|
|
2376
|
-
this.exprAllowed = false;
|
|
2377
|
-
|
|
2378
|
-
// Position after '<' (so next() reads '/')
|
|
2379
|
-
this.pos = closingStart + 1;
|
|
2380
|
-
this.type = tstt.jsxTagStart;
|
|
2381
|
-
this.start = closingStart;
|
|
2382
|
-
this.startLoc = closingStartLoc;
|
|
2383
|
-
this.next();
|
|
2384
|
-
|
|
2385
|
-
// Consume '/'
|
|
2386
|
-
this.next();
|
|
2387
|
-
|
|
2388
|
-
const closingElement = this.jsx_parseClosingElementAt(closingStart, closingStartLoc);
|
|
2389
|
-
element.closingElement = closingElement;
|
|
2390
|
-
this.exprAllowed = false;
|
|
2391
|
-
|
|
2392
|
-
const contentStartLineInfo = acorn.getLineInfo(this.input, start);
|
|
2393
|
-
const contentStartLoc = new acorn.Position(
|
|
2394
|
-
contentStartLineInfo.line,
|
|
2395
|
-
contentStartLineInfo.column,
|
|
2396
|
-
);
|
|
2397
|
-
|
|
2398
|
-
const contentEndLineInfo = acorn.getLineInfo(this.input, closingStart);
|
|
2399
|
-
const contentEndLoc = new acorn.Position(
|
|
2400
|
-
contentEndLineInfo.line,
|
|
2401
|
-
contentEndLineInfo.column,
|
|
2402
|
-
);
|
|
2403
|
-
|
|
2404
|
-
element.children = [
|
|
2405
|
-
/** @type {AST.ScriptContent} */ (
|
|
2406
|
-
/** @type {unknown} */ ({
|
|
2407
|
-
type: 'ScriptContent',
|
|
2408
|
-
content,
|
|
2409
|
-
start,
|
|
2410
|
-
end: closingStart,
|
|
2411
|
-
loc: { start: contentStartLoc, end: contentEndLoc },
|
|
2412
|
-
})
|
|
2413
|
-
),
|
|
2414
|
-
];
|
|
2415
|
-
|
|
2416
|
-
this.#path.pop();
|
|
2417
|
-
} else {
|
|
2418
|
-
// No closing tag
|
|
2419
|
-
this.#report_broken_markup_error(
|
|
2420
|
-
open.end,
|
|
2421
|
-
"Unclosed tag '<script>'. Expected '</script>' before end of template.",
|
|
2422
|
-
);
|
|
2423
|
-
/** @type {AST.Element} */ (element).unclosed = true;
|
|
2424
|
-
this.#path.pop();
|
|
2425
|
-
}
|
|
2426
|
-
} else if (/** @type {ESTreeJSX.JSXIdentifier} */ (open.name).name === 'style') {
|
|
2427
|
-
// jsx_parseOpeningElementAt treats ID selectors (ie. #myid) or type selectors (ie. div) as identifier and read it
|
|
2428
|
-
// So backtrack to the end of the <style> tag to make sure everything is included
|
|
2429
|
-
const start = open.end;
|
|
2430
|
-
const input = this.input.slice(start);
|
|
2431
|
-
const end = input.indexOf('</style>');
|
|
2432
|
-
const content = end === -1 ? input : input.slice(0, end);
|
|
2433
|
-
|
|
2434
|
-
const parsed_css = parse_style(content, { loose: this.#loose });
|
|
2435
|
-
if (!inside_head) {
|
|
2436
|
-
/** @type {AST.Element} */ (element).metadata.styleScopeHash = parsed_css.hash;
|
|
2437
|
-
}
|
|
2438
|
-
|
|
2439
|
-
const newLines = content.match(regex_newline_characters)?.length;
|
|
2440
|
-
if (newLines) {
|
|
2441
|
-
this.curLine = open.loc.end.line + newLines;
|
|
2442
|
-
this.lineStart = start + content.lastIndexOf('\n') + 1;
|
|
2443
|
-
}
|
|
2444
|
-
if (end !== -1) {
|
|
2445
|
-
const closingStart = start + content.length;
|
|
2446
|
-
const closingLineInfo = acorn.getLineInfo(this.input, closingStart);
|
|
2447
|
-
const closingStartLoc = new acorn.Position(
|
|
2448
|
-
closingLineInfo.line,
|
|
2449
|
-
closingLineInfo.column,
|
|
2450
|
-
);
|
|
2451
|
-
|
|
2452
|
-
// Ensure `</style>` can't be tokenized as `<` followed by a regexp
|
|
2453
|
-
// start when we manually advance to the `/`.
|
|
2454
|
-
this.exprAllowed = false;
|
|
2455
|
-
|
|
2456
|
-
// Position after '<' (so next() reads '/')
|
|
2457
|
-
this.pos = closingStart + 1;
|
|
2458
|
-
this.type = tstt.jsxTagStart;
|
|
2459
|
-
this.start = closingStart;
|
|
2460
|
-
this.startLoc = closingStartLoc;
|
|
2461
|
-
this.next();
|
|
2462
|
-
|
|
2463
|
-
// Consume '/'
|
|
2464
|
-
this.next();
|
|
2465
|
-
|
|
2466
|
-
const closingElement = this.jsx_parseClosingElementAt(closingStart, closingStartLoc);
|
|
2467
|
-
element.closingElement = closingElement;
|
|
2468
|
-
this.exprAllowed = false;
|
|
2469
|
-
this.#path.pop();
|
|
2470
|
-
} else {
|
|
2471
|
-
this.#report_broken_markup_error(
|
|
2472
|
-
open.end,
|
|
2473
|
-
"Unclosed tag '<style>'. Expected '</style>' before end of template.",
|
|
2474
|
-
);
|
|
2475
|
-
/** @type {AST.Element} */ (element).unclosed = true;
|
|
2476
|
-
this.#path.pop();
|
|
2477
|
-
}
|
|
2478
|
-
// This node is used for Prettier - always add parsed CSS as children
|
|
2479
|
-
// for proper formatting, regardless of whether it's inside head or not
|
|
2480
|
-
/** @type {AST.Element} */ (element).children = [
|
|
2481
|
-
/** @type {AST.Node} */ (/** @type {unknown} */ (parsed_css)),
|
|
2482
|
-
];
|
|
2483
|
-
|
|
2484
|
-
// Ensure we escape JSX <tag></tag> context
|
|
2485
|
-
const curContext = this.curContext();
|
|
2486
|
-
const parent = this.#path.at(-1);
|
|
2487
|
-
const insideTemplate = this.#isNativeTemplateNode(parent);
|
|
2488
|
-
|
|
2489
|
-
if (curContext === tstc.tc_expr && !insideTemplate) {
|
|
2490
|
-
this.context.pop();
|
|
2491
|
-
}
|
|
2492
|
-
|
|
2493
|
-
/** @type {AST.Element} */ (element).css = content;
|
|
2494
|
-
} else {
|
|
2495
|
-
this.#parseNativeTemplateBody(element, /** @type {AST.Element} */ (element).children, {
|
|
2496
|
-
enterScope: true,
|
|
2497
|
-
resetFunctionBodyDepth: true,
|
|
2498
|
-
});
|
|
2499
|
-
if (/** @type {AST.Tsx} */ (element).type === 'Tsx') {
|
|
2500
|
-
this.#reportDynamicJsxElementsInTsx(/** @type {AST.Element} */ (element).children);
|
|
2501
|
-
}
|
|
2502
|
-
|
|
2503
|
-
if (/** @type {AST.Tsx} */ (element).type === 'Tsx') {
|
|
2504
|
-
this.#path.pop();
|
|
2505
|
-
|
|
2506
|
-
if (!element.unclosed) {
|
|
2507
|
-
const raise_error = () => {
|
|
2508
|
-
this.raise(this.start, `Expected closing tag '</tsx>'`);
|
|
2509
|
-
};
|
|
2510
|
-
|
|
2511
|
-
this.next();
|
|
2512
|
-
// we should expect to see </tsx>
|
|
2513
|
-
if (this.value !== '/') {
|
|
2514
|
-
raise_error();
|
|
2515
|
-
}
|
|
2516
|
-
this.next();
|
|
2517
|
-
if (this.value !== 'tsx') {
|
|
2518
|
-
raise_error();
|
|
2519
|
-
}
|
|
2520
|
-
this.next();
|
|
2521
|
-
if (this.type !== tstt.jsxTagEnd) {
|
|
2522
|
-
raise_error();
|
|
2523
|
-
}
|
|
2524
|
-
this.#popTsxTokenContextBeforeTemplateExpressionChild();
|
|
2525
|
-
this.next();
|
|
2526
|
-
}
|
|
2527
|
-
} else if (/** @type {AST.TsxCompat} */ (element).type === 'TsxCompat') {
|
|
2528
|
-
this.#path.pop();
|
|
2529
|
-
|
|
2530
|
-
if (!element.unclosed) {
|
|
2531
|
-
const raise_error = () => {
|
|
2532
|
-
this.raise(
|
|
2533
|
-
this.start,
|
|
2534
|
-
`Expected closing tag '</tsx:${/** @type {AST.TsxCompat} */ (element).kind}>'`,
|
|
2535
|
-
);
|
|
2536
|
-
};
|
|
2537
|
-
|
|
2538
|
-
this.next();
|
|
2539
|
-
// we should expect to see </tsx:kind>
|
|
2540
|
-
if (this.value !== '/') {
|
|
2541
|
-
raise_error();
|
|
2542
|
-
}
|
|
2543
|
-
this.next();
|
|
2544
|
-
if (this.value !== 'tsx') {
|
|
2545
|
-
raise_error();
|
|
2546
|
-
}
|
|
2547
|
-
this.next();
|
|
2548
|
-
if (this.type.label !== ':') {
|
|
2549
|
-
raise_error();
|
|
2550
|
-
}
|
|
2551
|
-
this.next();
|
|
2552
|
-
if (this.value !== /** @type {AST.TsxCompat} */ (element).kind) {
|
|
2553
|
-
raise_error();
|
|
2554
|
-
}
|
|
2555
|
-
this.next();
|
|
2556
|
-
if (this.type !== tstt.jsxTagEnd) {
|
|
2557
|
-
raise_error();
|
|
2558
|
-
}
|
|
2559
|
-
this.#popTsxTokenContextBeforeTemplateExpressionChild();
|
|
2560
|
-
this.next();
|
|
2561
|
-
}
|
|
2562
|
-
} else if (
|
|
2563
|
-
/** @type {AST.Tsrx} */ (element).type === 'Tsrx' &&
|
|
2564
|
-
this.#path[this.#path.length - 1] === element
|
|
2565
|
-
) {
|
|
2566
|
-
const displayTag = element.openingElement.name ? 'tsrx' : '';
|
|
2567
|
-
this.#report_broken_markup_error(
|
|
2568
|
-
this.start,
|
|
2569
|
-
`Unclosed tag '<${displayTag}>'. Expected '</${displayTag}>' before end of template.`,
|
|
2570
|
-
);
|
|
2571
|
-
element.unclosed = true;
|
|
2572
|
-
/** @type {AST.SourceLocation} */ (element.loc).end = {
|
|
2573
|
-
.../** @type {AST.SourceLocation} */ (element.openingElement.loc).end,
|
|
2574
|
-
};
|
|
2575
|
-
element.end = element.openingElement.end;
|
|
2576
|
-
this.#path.pop();
|
|
2577
|
-
} else if (
|
|
2578
|
-
element.type === 'Element' &&
|
|
2579
|
-
this.#path[this.#path.length - 1] === element
|
|
2580
|
-
) {
|
|
2581
|
-
// Check if this element was properly closed
|
|
2582
|
-
const tagName = this.getElementName(element.id);
|
|
2583
|
-
this.#report_broken_markup_error(
|
|
2584
|
-
this.start,
|
|
2585
|
-
`Unclosed tag '<${tagName}>'. Expected '</${tagName}>' before end of template.`,
|
|
2586
|
-
);
|
|
2587
|
-
element.unclosed = true;
|
|
2588
|
-
/** @type {AST.SourceLocation} */ (element.loc).end = {
|
|
2589
|
-
.../** @type {AST.SourceLocation} */ (element.openingElement.loc).end,
|
|
2590
|
-
};
|
|
2591
|
-
element.end = element.openingElement.end;
|
|
2592
|
-
this.#path.pop();
|
|
2593
|
-
}
|
|
4063
|
+
node.end = is_fragment
|
|
4064
|
+
? /** @type {ESTreeJSX.JSXFragment} */ (node).openingFragment.end
|
|
4065
|
+
: /** @type {ESTreeJSX.JSXElement} */ (node).openingElement.end;
|
|
4066
|
+
this.#path.pop();
|
|
2594
4067
|
}
|
|
2595
4068
|
|
|
2596
|
-
//
|
|
2597
|
-
|
|
4069
|
+
// A balanced element must leave the tokenizer context exactly where it
|
|
4070
|
+
// began. The body (especially a control-flow block) can leave residue
|
|
4071
|
+
// above the children context — the children tc_expr plus a spurious
|
|
4072
|
+
// b_stat from an @if/@for block save-restore — which the old single
|
|
4073
|
+
// tc_expr pop missed when the b_stat sat on top. In expression position,
|
|
4074
|
+
// unwind back to the pre-element depth so the following JS token (e.g. a
|
|
4075
|
+
// comma/brace closing an enclosing object) tokenizes as code, not text.
|
|
2598
4076
|
const parent = this.#path.at(-1);
|
|
2599
4077
|
const insideTemplate = this.#isNativeTemplateNode(parent);
|
|
2600
4078
|
|
|
2601
|
-
if (
|
|
2602
|
-
this.context.
|
|
4079
|
+
if (!insideTemplate && this.context.length > pre_element_context_depth) {
|
|
4080
|
+
this.context.length = pre_element_context_depth;
|
|
2603
4081
|
}
|
|
2604
4082
|
}
|
|
2605
4083
|
|
|
2606
|
-
if (
|
|
2607
|
-
/** @type {
|
|
2608
|
-
|
|
4084
|
+
if (is_style && /** @type {AST.JSXStyleElement} */ (node).closingElement) {
|
|
4085
|
+
const closing = /** @type {ESTreeJSX.JSXClosingElement & AST.NodeWithLocation} */ (
|
|
4086
|
+
/** @type {AST.JSXStyleElement} */ (node).closingElement
|
|
2609
4087
|
);
|
|
4088
|
+
return this.finishNodeAt(node, node.type, closing.end, closing.loc.end);
|
|
2610
4089
|
}
|
|
2611
4090
|
|
|
2612
|
-
this.finishNode(
|
|
2613
|
-
return element;
|
|
4091
|
+
return this.finishNode(node, node.type);
|
|
2614
4092
|
}
|
|
2615
4093
|
|
|
2616
4094
|
/**
|
|
2617
4095
|
* @type {Parse.Parser['parseTemplateBody']}
|
|
2618
4096
|
*/
|
|
2619
4097
|
parseTemplateBody(body) {
|
|
2620
|
-
const
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
if (
|
|
2632
|
-
|
|
2633
|
-
|
|
4098
|
+
const current_template_node = this.#currentNativeTemplateNode();
|
|
4099
|
+
if (!current_template_node) return;
|
|
4100
|
+
// Outside a `@{ … }` block every element/fragment body is plain JSX (§2,
|
|
4101
|
+
// §5). There is no script section and no `---` fence to infer — text is
|
|
4102
|
+
// text, and setup code lives only inside a code block.
|
|
4103
|
+
current_template_node.metadata ??= { path: [] };
|
|
4104
|
+
current_template_node.metadata.templateMode = 'template';
|
|
4105
|
+
|
|
4106
|
+
// `@{ … }` code block as element/fragment content (§2 rule 1). Sibling
|
|
4107
|
+
// code blocks are allowed, so this is not gated on an empty body;
|
|
4108
|
+
// reposition onto the `@` if leading whitespace was tokenized ahead of it.
|
|
4109
|
+
if (this.#atCodeBlockStart()) {
|
|
4110
|
+
const at_index = skip_whitespace_from(this.input, this.start);
|
|
4111
|
+
if (this.start !== at_index) {
|
|
4112
|
+
const loc = acorn.getLineInfo(this.input, at_index);
|
|
4113
|
+
this.pos = at_index;
|
|
4114
|
+
this.start = at_index;
|
|
4115
|
+
this.startLoc = new acorn.Position(loc.line, loc.column);
|
|
4116
|
+
this.curLine = loc.line;
|
|
4117
|
+
this.lineStart = at_index - loc.column;
|
|
2634
4118
|
}
|
|
2635
|
-
|
|
2636
|
-
this.next();
|
|
4119
|
+
body.push(/** @type {any} */ (this.#parseCodeBlock()));
|
|
2637
4120
|
this.parseTemplateBody(body);
|
|
2638
4121
|
return;
|
|
2639
4122
|
}
|
|
2640
4123
|
|
|
2641
|
-
if (!inside_func) {
|
|
2642
|
-
if (this.type.label === 'continue') {
|
|
2643
|
-
throw new Error('`continue` statements are not allowed in native templates');
|
|
2644
|
-
}
|
|
2645
|
-
if (this.type.label === 'break') {
|
|
2646
|
-
throw new Error('`break` statements are not allowed in native templates');
|
|
2647
|
-
}
|
|
2648
|
-
}
|
|
2649
|
-
|
|
2650
|
-
if (inside_tsx_island) {
|
|
2651
|
-
this.#parseTsxIslandBody(
|
|
2652
|
-
/** @type {AST.Tsx | AST.TsxCompat} */ (inside_tsx_island),
|
|
2653
|
-
/** @type {AST.Node[]} */ (/** @type {unknown} */ (body)),
|
|
2654
|
-
);
|
|
2655
|
-
return;
|
|
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
|
-
}
|
|
2667
4124
|
if (this.type === tt.braceL) {
|
|
2668
4125
|
body.push(this.#parseNativeTemplateExpressionContainer());
|
|
2669
|
-
} else if (
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
4126
|
+
} else if (this.type === tstt.jsxText) {
|
|
4127
|
+
// A nested element with its own body can leak a JSX expression context,
|
|
4128
|
+
// so the whitespace after its closing tag is mis-tokenized as a stale
|
|
4129
|
+
// text token whose start was advanced onto the following `<`. Real JSX
|
|
4130
|
+
// text never starts at `<`, so drop the leaked context and re-read the
|
|
4131
|
+
// tag instead of emitting an empty node.
|
|
4132
|
+
if (this.input.charCodeAt(this.start) === CharCode.lessThan) {
|
|
4133
|
+
while (this.curContext() === tstc.tc_expr) {
|
|
4134
|
+
this.context.pop();
|
|
4135
|
+
}
|
|
4136
|
+
this.pos = this.start;
|
|
4137
|
+
this.exprAllowed = true;
|
|
4138
|
+
this.next();
|
|
4139
|
+
this.parseTemplateBody(body);
|
|
4140
|
+
return;
|
|
4141
|
+
}
|
|
4142
|
+
const text = this.#parseTemplateRawText();
|
|
4143
|
+
if (this.#shouldKeepTemplateTextNode(text)) {
|
|
4144
|
+
body.push(text);
|
|
4145
|
+
}
|
|
4146
|
+
} else if (this.#isJSXControlFlowDirectiveStart()) {
|
|
4147
|
+
body.push(this.#parseJSXControlFlowExpression());
|
|
2674
4148
|
} else if (this.type === tt.braceR) {
|
|
2675
4149
|
// Leaving a native template body. We may still be in TSX/JSX tokenization
|
|
2676
4150
|
// context (e.g. after parsing markup), but the closing `}` is a JS token.
|
|
@@ -2682,8 +4156,7 @@ export function TSRXPlugin(config) {
|
|
|
2682
4156
|
return;
|
|
2683
4157
|
} else if (
|
|
2684
4158
|
this.type === tstt.jsxTagStart ||
|
|
2685
|
-
|
|
2686
|
-
this.input.charCodeAt(this.start + 1) === CharCode.slash)
|
|
4159
|
+
this.input.charCodeAt(this.start) === CharCode.lessThan
|
|
2687
4160
|
) {
|
|
2688
4161
|
const startPos = this.start;
|
|
2689
4162
|
const startLoc = this.startLoc;
|
|
@@ -2704,21 +4177,20 @@ export function TSRXPlugin(config) {
|
|
|
2704
4177
|
// Consume '/'
|
|
2705
4178
|
this.next();
|
|
2706
4179
|
|
|
2707
|
-
|
|
2708
|
-
|
|
4180
|
+
let closingElement;
|
|
4181
|
+
this.#closingNativeTemplateNode = true;
|
|
4182
|
+
try {
|
|
4183
|
+
closingElement = /** @type {ESTreeJSX.JSXClosingElement & AST.NodeWithLocation} */ (
|
|
2709
4184
|
this.jsx_parseClosingElementAt(startPos, startLoc)
|
|
2710
4185
|
);
|
|
4186
|
+
} finally {
|
|
4187
|
+
this.#closingNativeTemplateNode = false;
|
|
4188
|
+
}
|
|
2711
4189
|
this.exprAllowed = false;
|
|
2712
4190
|
|
|
2713
4191
|
// Validate that the closing tag matches the opening tag
|
|
2714
|
-
const currentElement = this.#path[this.#path.length - 1];
|
|
2715
|
-
if (
|
|
2716
|
-
!currentElement ||
|
|
2717
|
-
(currentElement.type !== 'Element' &&
|
|
2718
|
-
currentElement.type !== 'Tsx' &&
|
|
2719
|
-
currentElement.type !== 'Tsrx' &&
|
|
2720
|
-
currentElement.type !== 'TsxCompat')
|
|
2721
|
-
) {
|
|
4192
|
+
const currentElement = /** @type {any} */ (this.#path[this.#path.length - 1]);
|
|
4193
|
+
if (!this.#isNativeTemplateNode(currentElement)) {
|
|
2722
4194
|
this.raise(this.start, 'Unexpected closing tag');
|
|
2723
4195
|
}
|
|
2724
4196
|
|
|
@@ -2727,27 +4199,17 @@ export function TSRXPlugin(config) {
|
|
|
2727
4199
|
/** @type {string | null} */
|
|
2728
4200
|
let closingTagName;
|
|
2729
4201
|
|
|
2730
|
-
if (currentElement.type === '
|
|
2731
|
-
openingTagName = 'tsx:' + currentElement.kind;
|
|
2732
|
-
closingTagName =
|
|
2733
|
-
closingElement.name?.type === 'JSXNamespacedName'
|
|
2734
|
-
? closingElement.name.namespace.name + ':' + closingElement.name.name.name
|
|
2735
|
-
: this.getElementName(closingElement.name);
|
|
2736
|
-
} else if (currentElement.type === 'Tsx') {
|
|
2737
|
-
openingTagName = currentElement.openingElement.name ? 'tsx' : null;
|
|
2738
|
-
closingTagName =
|
|
2739
|
-
closingElement.name?.type === 'JSXNamespacedName'
|
|
2740
|
-
? closingElement.name.namespace.name + ':' + closingElement.name.name.name
|
|
2741
|
-
: this.getElementName(closingElement.name);
|
|
2742
|
-
} else if (currentElement.type === 'Tsrx') {
|
|
4202
|
+
if (currentElement.type === 'JSXFragment') {
|
|
2743
4203
|
openingTagName = '';
|
|
2744
|
-
closingTagName =
|
|
2745
|
-
|
|
4204
|
+
closingTagName = !closingElement.name
|
|
4205
|
+
? ''
|
|
4206
|
+
: closingElement.name.type === 'JSXNamespacedName'
|
|
2746
4207
|
? closingElement.name.namespace.name + ':' + closingElement.name.name.name
|
|
2747
4208
|
: this.getElementName(closingElement.name);
|
|
2748
4209
|
} else {
|
|
2749
|
-
|
|
2750
|
-
|
|
4210
|
+
openingTagName = currentElement.openingElement?.name
|
|
4211
|
+
? this.getElementName(currentElement.openingElement.name)
|
|
4212
|
+
: null;
|
|
2751
4213
|
closingTagName = closingElement.name
|
|
2752
4214
|
? closingElement.name?.type === 'JSXNamespacedName'
|
|
2753
4215
|
? closingElement.name.namespace.name + ':' + closingElement.name.name.name
|
|
@@ -2756,6 +4218,24 @@ export function TSRXPlugin(config) {
|
|
|
2756
4218
|
}
|
|
2757
4219
|
|
|
2758
4220
|
if (openingTagName !== closingTagName) {
|
|
4221
|
+
// A closing tag that matches no open element on the path is not a
|
|
4222
|
+
// mismatch we can recover from by marking ancestors unclosed — it is
|
|
4223
|
+
// simply an unexpected closing tag (e.g. `<div></span>`).
|
|
4224
|
+
const normalized_closing_name = closingTagName ?? '';
|
|
4225
|
+
const matches_open_element = this.#path.some((node) => {
|
|
4226
|
+
const elem = /** @type {any} */ (node);
|
|
4227
|
+
if (!this.#isNativeTemplateNode(elem)) return false;
|
|
4228
|
+
const elemName =
|
|
4229
|
+
elem.type === 'JSXFragment'
|
|
4230
|
+
? ''
|
|
4231
|
+
: elem.openingElement?.name
|
|
4232
|
+
? this.getElementName(elem.openingElement.name)
|
|
4233
|
+
: null;
|
|
4234
|
+
return elemName === normalized_closing_name;
|
|
4235
|
+
});
|
|
4236
|
+
if (!matches_open_element && this.#collect) {
|
|
4237
|
+
this.raise(closingElement.start, 'Unexpected closing tag');
|
|
4238
|
+
}
|
|
2759
4239
|
// this will throw if not collecting errors
|
|
2760
4240
|
this.#report_broken_markup_error(
|
|
2761
4241
|
closingElement.start,
|
|
@@ -2764,30 +4244,19 @@ export function TSRXPlugin(config) {
|
|
|
2764
4244
|
);
|
|
2765
4245
|
// Loop through all unclosed elements on the stack
|
|
2766
4246
|
while (this.#path.length > 0) {
|
|
2767
|
-
const elem = this.#path[this.#path.length - 1];
|
|
4247
|
+
const elem = /** @type {any} */ (this.#path[this.#path.length - 1]);
|
|
2768
4248
|
|
|
2769
4249
|
// Stop at non-template boundaries.
|
|
2770
|
-
if (
|
|
2771
|
-
elem.type !== 'Element' &&
|
|
2772
|
-
elem.type !== 'Tsx' &&
|
|
2773
|
-
elem.type !== 'Tsrx' &&
|
|
2774
|
-
elem.type !== 'TsxCompat'
|
|
2775
|
-
) {
|
|
4250
|
+
if (!this.#isNativeTemplateNode(elem)) {
|
|
2776
4251
|
break;
|
|
2777
4252
|
}
|
|
2778
4253
|
|
|
2779
4254
|
const elemName =
|
|
2780
|
-
elem.type === '
|
|
2781
|
-
? '
|
|
2782
|
-
: elem.
|
|
2783
|
-
? elem.openingElement.name
|
|
2784
|
-
|
|
2785
|
-
: null
|
|
2786
|
-
: elem.type === 'Tsrx'
|
|
2787
|
-
? ''
|
|
2788
|
-
: elem.id
|
|
2789
|
-
? this.getElementName(elem.id)
|
|
2790
|
-
: null;
|
|
4255
|
+
elem.type === 'JSXFragment'
|
|
4256
|
+
? ''
|
|
4257
|
+
: elem.openingElement?.name
|
|
4258
|
+
? this.getElementName(elem.openingElement.name)
|
|
4259
|
+
: null;
|
|
2791
4260
|
|
|
2792
4261
|
// Found matching opening tag
|
|
2793
4262
|
if (elemName === closingTagName) {
|
|
@@ -2797,28 +4266,31 @@ export function TSRXPlugin(config) {
|
|
|
2797
4266
|
// Mark as unclosed and adjust location
|
|
2798
4267
|
elem.unclosed = true;
|
|
2799
4268
|
/** @type {AST.NodeWithLocation} */ (elem).loc.end = {
|
|
2800
|
-
.../** @type {AST.SourceLocation} */ (
|
|
4269
|
+
.../** @type {AST.SourceLocation} */ (
|
|
4270
|
+
elem.type === 'JSXFragment' ? elem.openingFragment.loc : elem.openingElement.loc
|
|
4271
|
+
).end,
|
|
2801
4272
|
};
|
|
2802
|
-
elem.end =
|
|
4273
|
+
elem.end =
|
|
4274
|
+
elem.type === 'JSXFragment' ? elem.openingFragment.end : elem.openingElement.end;
|
|
2803
4275
|
|
|
2804
4276
|
this.#path.pop(); // Remove from stack
|
|
2805
4277
|
}
|
|
2806
4278
|
}
|
|
2807
4279
|
|
|
2808
|
-
const elementToClose = this.#path[this.#path.length - 1];
|
|
2809
|
-
if (
|
|
2810
|
-
elementToClose &&
|
|
2811
|
-
(elementToClose.type === 'Element' || elementToClose.type === 'Tsrx')
|
|
2812
|
-
) {
|
|
4280
|
+
const elementToClose = /** @type {any} */ (this.#path[this.#path.length - 1]);
|
|
4281
|
+
if (this.#isNativeTemplateNode(elementToClose)) {
|
|
2813
4282
|
const elementToCloseName =
|
|
2814
|
-
elementToClose.type === '
|
|
4283
|
+
elementToClose.type === 'JSXFragment'
|
|
2815
4284
|
? ''
|
|
2816
|
-
:
|
|
2817
|
-
? this.getElementName(
|
|
4285
|
+
: elementToClose.openingElement?.name
|
|
4286
|
+
? this.getElementName(elementToClose.openingElement.name)
|
|
2818
4287
|
: null;
|
|
2819
4288
|
if (elementToCloseName === closingTagName) {
|
|
2820
|
-
|
|
2821
|
-
closingElement;
|
|
4289
|
+
if (elementToClose.type === 'JSXFragment') {
|
|
4290
|
+
elementToClose.closingFragment = this.#toClosingFragment(closingElement);
|
|
4291
|
+
} else {
|
|
4292
|
+
elementToClose.closingElement = closingElement;
|
|
4293
|
+
}
|
|
2822
4294
|
}
|
|
2823
4295
|
}
|
|
2824
4296
|
|
|
@@ -2830,16 +4302,12 @@ export function TSRXPlugin(config) {
|
|
|
2830
4302
|
if (node !== null) {
|
|
2831
4303
|
body.push(node);
|
|
2832
4304
|
}
|
|
4305
|
+
} else if (this.type === tt.eof) {
|
|
4306
|
+
return;
|
|
2833
4307
|
} else {
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
body.push(node);
|
|
2838
|
-
|
|
2839
|
-
// Ensure we're not in JSX context before recursing
|
|
2840
|
-
// This is important when elements are parsed at statement level
|
|
2841
|
-
if (this.curContext() === tstc.tc_expr) {
|
|
2842
|
-
this.context.pop();
|
|
4308
|
+
const text = this.#parseTemplateRawText();
|
|
4309
|
+
if (this.#shouldKeepTemplateTextNode(text)) {
|
|
4310
|
+
body.push(text);
|
|
2843
4311
|
}
|
|
2844
4312
|
}
|
|
2845
4313
|
|
|
@@ -2918,22 +4386,30 @@ export function TSRXPlugin(config) {
|
|
|
2918
4386
|
this.type === tt.braceL &&
|
|
2919
4387
|
this.context.some((c) => c === tstc.tc_expr)
|
|
2920
4388
|
) {
|
|
2921
|
-
return /** @type {ESTreeJSX.
|
|
2922
|
-
|
|
4389
|
+
return /** @type {ESTreeJSX.JSXExpressionContainer} */ (
|
|
4390
|
+
this.#parseNativeTemplateExpressionContainer()
|
|
2923
4391
|
);
|
|
2924
4392
|
}
|
|
2925
4393
|
|
|
2926
4394
|
if (this.type === tstt.jsxTagStart) {
|
|
2927
|
-
this
|
|
2928
|
-
|
|
2929
|
-
|
|
4395
|
+
if (this.#forceScriptJSXElementDepth > 0) {
|
|
4396
|
+
return /** @type {AST.Statement} */ (
|
|
4397
|
+
/** @type {unknown} */ (super.parseStatement(context, topLevel, exports))
|
|
4398
|
+
);
|
|
2930
4399
|
}
|
|
4400
|
+
|
|
4401
|
+
this.next();
|
|
4402
|
+
if (this.value === '/') this.unexpected();
|
|
2931
4403
|
const node = this.parseElement();
|
|
2932
4404
|
|
|
2933
4405
|
if (!node) {
|
|
2934
4406
|
this.unexpected();
|
|
2935
4407
|
}
|
|
2936
|
-
if (
|
|
4408
|
+
if (
|
|
4409
|
+
this.#functionBodyDepth > 0 &&
|
|
4410
|
+
node.type === 'JSXFragment' &&
|
|
4411
|
+
this.curContext() === b_stat
|
|
4412
|
+
) {
|
|
2937
4413
|
this.context.pop();
|
|
2938
4414
|
if (this.curContext() === tstc.tc_expr) {
|
|
2939
4415
|
this.context.pop();
|
|
@@ -2945,19 +4421,6 @@ export function TSRXPlugin(config) {
|
|
|
2945
4421
|
return node;
|
|
2946
4422
|
}
|
|
2947
4423
|
|
|
2948
|
-
if (
|
|
2949
|
-
this.#functionBodyDepth === 0 &&
|
|
2950
|
-
this.type === tt.string &&
|
|
2951
|
-
this.input.charCodeAt(this.start) === CharCode.doubleQuote &&
|
|
2952
|
-
(this.#path.at(-1)?.type === 'Element' || this.#path.at(-1)?.type === 'Tsrx')
|
|
2953
|
-
) {
|
|
2954
|
-
this.pos = this.start;
|
|
2955
|
-
this.#readDoubleQuotedTextChildToken();
|
|
2956
|
-
const node = this.parseDoubleQuotedTextChild();
|
|
2957
|
-
this.semicolon();
|
|
2958
|
-
return node;
|
|
2959
|
-
}
|
|
2960
|
-
|
|
2961
4424
|
// &[ or &{ at statement level — lazy destructuring assignment
|
|
2962
4425
|
// e.g., &[data] = track(0); or &{x, y} = obj;
|
|
2963
4426
|
if (this.type === tt.bitwiseAND) {
|
|
@@ -2998,30 +4461,28 @@ export function TSRXPlugin(config) {
|
|
|
2998
4461
|
parseBlock(createNewLexicalScope, node, exitStrict) {
|
|
2999
4462
|
const parent = this.#path.at(-1);
|
|
3000
4463
|
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
) {
|
|
3008
|
-
if (createNewLexicalScope === void 0) createNewLexicalScope = true;
|
|
3009
|
-
if (node === void 0) node = /** @type {AST.BlockStatement} */ (this.startNode());
|
|
3010
|
-
|
|
3011
|
-
node.body = [];
|
|
3012
|
-
this.#allowDoubleQuotedTextChildAfterBrace = true;
|
|
3013
|
-
this.expect(tt.braceL);
|
|
3014
|
-
this.#parseNativeTemplateBody(node, node.body, {
|
|
3015
|
-
enterScope: createNewLexicalScope,
|
|
3016
|
-
});
|
|
3017
|
-
|
|
3018
|
-
if (exitStrict) {
|
|
3019
|
-
this.strict = false;
|
|
4464
|
+
if (this.#isNativeTemplateNode(parent) && this.#templateControlFlowBlockDepth > 0) {
|
|
4465
|
+
this.#templateControlFlowBlockDepth--;
|
|
4466
|
+
try {
|
|
4467
|
+
return this.#parseTemplateControlFlowBlock(createNewLexicalScope, node, exitStrict);
|
|
4468
|
+
} finally {
|
|
4469
|
+
this.#templateControlFlowBlockDepth++;
|
|
3020
4470
|
}
|
|
3021
|
-
|
|
4471
|
+
}
|
|
3022
4472
|
|
|
3023
|
-
|
|
3024
|
-
|
|
4473
|
+
if (this.#functionBodyDepth > 0 && this.#isNativeTemplateNode(parent)) {
|
|
4474
|
+
let pushed_statement_context = false;
|
|
4475
|
+
if (this.curContext() !== b_stat) {
|
|
4476
|
+
this.context.push(b_stat);
|
|
4477
|
+
pushed_statement_context = true;
|
|
4478
|
+
}
|
|
4479
|
+
try {
|
|
4480
|
+
return super.parseBlock(createNewLexicalScope, node, exitStrict);
|
|
4481
|
+
} finally {
|
|
4482
|
+
if (pushed_statement_context && this.curContext() === b_stat) {
|
|
4483
|
+
this.context.pop();
|
|
4484
|
+
}
|
|
4485
|
+
}
|
|
3025
4486
|
}
|
|
3026
4487
|
|
|
3027
4488
|
return super.parseBlock(createNewLexicalScope, node, exitStrict);
|