@tsrx/core 0.1.31 → 0.1.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/constants.js +4 -0
- package/src/index.js +4 -1
- package/src/plugin.js +152 -88
- package/src/transform/jsx/helpers.js +14 -39
- package/src/transform/jsx/index.js +408 -47
- package/src/transform/lazy.js +95 -46
- package/src/utils/builders.js +20 -0
- package/types/index.d.ts +6 -0
package/package.json
CHANGED
package/src/constants.js
CHANGED
|
@@ -2,6 +2,10 @@ export const TEMPLATE_FRAGMENT = 1;
|
|
|
2
2
|
export const TEMPLATE_USE_IMPORT_NODE = 1 << 1;
|
|
3
3
|
export const IS_CONTROLLED = 1 << 2;
|
|
4
4
|
export const IS_INDEXED = 1 << 3;
|
|
5
|
+
// A control-flow block that is its component's sole root: renders before the
|
|
6
|
+
// parent `__anchor` (sibling semantics, no `<!>` wrapper). Must match the value
|
|
7
|
+
// in ripple's runtime constants.
|
|
8
|
+
export const ROOT_CONTROLLED = 1 << 4;
|
|
5
9
|
export const TEMPLATE_SVG_NAMESPACE = 1 << 5;
|
|
6
10
|
export const TEMPLATE_MATHML_NAMESPACE = 1 << 6;
|
|
7
11
|
|
package/src/index.js
CHANGED
|
@@ -31,6 +31,7 @@ export {
|
|
|
31
31
|
TEMPLATE_USE_IMPORT_NODE,
|
|
32
32
|
IS_CONTROLLED,
|
|
33
33
|
IS_INDEXED,
|
|
34
|
+
ROOT_CONTROLLED,
|
|
34
35
|
TEMPLATE_SVG_NAMESPACE,
|
|
35
36
|
TEMPLATE_MATHML_NAMESPACE,
|
|
36
37
|
HYDRATION_START,
|
|
@@ -147,6 +148,7 @@ export {
|
|
|
147
148
|
create_hook_safe_helper as createHookSafeHelper,
|
|
148
149
|
create_element_ref_target_type as createElementRefTargetType,
|
|
149
150
|
create_element_ref_target_type_for_name as createElementRefTargetTypeForName,
|
|
151
|
+
build_return_expression as buildReturnExpression,
|
|
150
152
|
createJsxTransform,
|
|
151
153
|
extract_jsx_setup_declarations as extractJsxSetupDeclarations,
|
|
152
154
|
is_component_like_element,
|
|
@@ -159,10 +161,11 @@ export {
|
|
|
159
161
|
rewrite_loop_continues_to_bare_returns as rewriteLoopContinuesToBareReturns,
|
|
160
162
|
to_jsx_attribute as toJsxAttribute,
|
|
161
163
|
validate_at_most_one_ref_attribute as validateAtMostOneRefAttribute,
|
|
164
|
+
wrap_edge_whitespace as wrapEdgeWhitespace,
|
|
162
165
|
} from './transform/jsx/index.js';
|
|
163
166
|
export {
|
|
164
167
|
in_jsx_child_context as inJsxChildContext,
|
|
165
|
-
|
|
168
|
+
is_empty_jsx_fragment as isEmptyJsxFragment,
|
|
166
169
|
tsx_with_ts_locations as tsxWithTsLocations,
|
|
167
170
|
is_template_if_node as isTemplateIfNode,
|
|
168
171
|
is_template_for_of_node as isTemplateForOfNode,
|
package/src/plugin.js
CHANGED
|
@@ -5,12 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import * as acorn from 'acorn';
|
|
8
|
-
import {
|
|
9
|
-
skipWhitespace,
|
|
10
|
-
isWhitespaceTextNode,
|
|
11
|
-
BINDING_TYPES,
|
|
12
|
-
DestructuringErrors,
|
|
13
|
-
} from './parse/index.js';
|
|
8
|
+
import { isWhitespaceTextNode, BINDING_TYPES, DestructuringErrors } from './parse/index.js';
|
|
14
9
|
import { parse_style } from './parse/style.js';
|
|
15
10
|
import { regex_newline_characters } from './utils/patterns.js';
|
|
16
11
|
import { error } from './errors.js';
|
|
@@ -237,6 +232,7 @@ export function TSRXPlugin(config) {
|
|
|
237
232
|
// If we push an undefined context, Acorn's tokenizer will later crash reading `.override`.
|
|
238
233
|
const b_stat = tc.b_stat || acorn.tokContexts.b_stat;
|
|
239
234
|
const b_expr = tc.b_expr || acorn.tokContexts.b_expr;
|
|
235
|
+
const q_tmpl = tc.q_tmpl || acorn.tokContexts.q_tmpl;
|
|
240
236
|
const tstt = Parser.acornTypeScript.tokTypes;
|
|
241
237
|
const tstc = Parser.acornTypeScript.tokContexts;
|
|
242
238
|
|
|
@@ -549,7 +545,6 @@ export function TSRXPlugin(config) {
|
|
|
549
545
|
while (index < this.input.length) {
|
|
550
546
|
if (this.#isTemplateLineCommentStart(index, start)) {
|
|
551
547
|
const comment_start = index;
|
|
552
|
-
const comment_start_loc = acorn.getLineInfo(this.input, comment_start);
|
|
553
548
|
index += 2;
|
|
554
549
|
while (
|
|
555
550
|
index < this.input.length &&
|
|
@@ -558,20 +553,9 @@ export function TSRXPlugin(config) {
|
|
|
558
553
|
) {
|
|
559
554
|
index++;
|
|
560
555
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
// as a leading comment on the following child (which the JSX printers
|
|
565
|
-
// emit), rather than on the container's `elementLeadingComments`.
|
|
566
|
-
this.options.onComment(
|
|
567
|
-
false,
|
|
568
|
-
this.input.slice(comment_start + 2, index),
|
|
569
|
-
comment_start,
|
|
570
|
-
index,
|
|
571
|
-
new acorn.Position(comment_start_loc.line, comment_start_loc.column),
|
|
572
|
-
new acorn.Position(comment_end_loc.line, comment_end_loc.column),
|
|
573
|
-
/** @type {any} */ (null),
|
|
574
|
-
);
|
|
556
|
+
|
|
557
|
+
if (comment_start >= token_end) {
|
|
558
|
+
this.#emitTemplateLineComment(comment_start, index, null);
|
|
575
559
|
}
|
|
576
560
|
continue;
|
|
577
561
|
}
|
|
@@ -590,7 +574,7 @@ export function TSRXPlugin(config) {
|
|
|
590
574
|
index,
|
|
591
575
|
new acorn.Position(comment_start_loc.line, comment_start_loc.column),
|
|
592
576
|
new acorn.Position(comment_end_loc.line, comment_end_loc.column),
|
|
593
|
-
|
|
577
|
+
null,
|
|
594
578
|
);
|
|
595
579
|
}
|
|
596
580
|
continue;
|
|
@@ -640,6 +624,62 @@ export function TSRXPlugin(config) {
|
|
|
640
624
|
return node.value !== '' && !regex_newline_characters.test(node.value);
|
|
641
625
|
}
|
|
642
626
|
|
|
627
|
+
#skipTrailingLayoutWhitespace() {
|
|
628
|
+
let index = this.start;
|
|
629
|
+
let has_newline = false;
|
|
630
|
+
while (index < this.input.length) {
|
|
631
|
+
const ch = this.input.charCodeAt(index);
|
|
632
|
+
if (ch === CharCode.lineFeed || ch === CharCode.carriageReturn) {
|
|
633
|
+
has_newline = true;
|
|
634
|
+
index++;
|
|
635
|
+
} else if (ch === CharCode.space || ch === CharCode.tab) {
|
|
636
|
+
index++;
|
|
637
|
+
} else if (ch === CharCode.slash && this.input.charCodeAt(index + 1) === CharCode.slash) {
|
|
638
|
+
const comment_start = index;
|
|
639
|
+
while (index < this.input.length && !this.#isNewlineCharCode(index)) {
|
|
640
|
+
index++;
|
|
641
|
+
}
|
|
642
|
+
this.#emitTemplateLineComment(comment_start, index, null);
|
|
643
|
+
} else {
|
|
644
|
+
break;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
if (!has_newline) return;
|
|
648
|
+
const loc = acorn.getLineInfo(this.input, index);
|
|
649
|
+
this.start = index;
|
|
650
|
+
this.startLoc = new acorn.Position(loc.line, loc.column);
|
|
651
|
+
if (this.pos <= index) {
|
|
652
|
+
this.curLine = loc.line;
|
|
653
|
+
this.lineStart = index - loc.column;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* @param {number} index
|
|
659
|
+
*/
|
|
660
|
+
#isNewlineCharCode(index) {
|
|
661
|
+
const ch = this.input.charCodeAt(index);
|
|
662
|
+
return ch === CharCode.lineFeed || ch === CharCode.carriageReturn;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* @param {number} start
|
|
667
|
+
* @param {number} end
|
|
668
|
+
* @param {Parse.CommentMetaData | null} metadata
|
|
669
|
+
*/
|
|
670
|
+
#emitTemplateLineComment(start, end, metadata) {
|
|
671
|
+
if (!this.options.onComment) return;
|
|
672
|
+
this.options.onComment(
|
|
673
|
+
false,
|
|
674
|
+
this.input.slice(start + 2, end),
|
|
675
|
+
start,
|
|
676
|
+
end,
|
|
677
|
+
acorn.getLineInfo(this.input, start),
|
|
678
|
+
acorn.getLineInfo(this.input, end),
|
|
679
|
+
metadata,
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
|
|
643
683
|
#isSwitchCaseScriptStatementStart() {
|
|
644
684
|
let index = skip_whitespace_from(this.input, this.start);
|
|
645
685
|
|
|
@@ -1233,14 +1273,21 @@ export function TSRXPlugin(config) {
|
|
|
1233
1273
|
*/
|
|
1234
1274
|
#parseCodeBlockSetupStatement() {
|
|
1235
1275
|
const previous_context = this.context;
|
|
1236
|
-
this.
|
|
1237
|
-
(context) =>
|
|
1238
|
-
context !== tstc.tc_expr && context !== tstc.tc_oTag && context !== tstc.tc_cTag,
|
|
1239
|
-
);
|
|
1276
|
+
const at_template_literal = this.type === tt.backQuote;
|
|
1240
1277
|
let pushed_statement_context = false;
|
|
1241
|
-
if (
|
|
1242
|
-
this.
|
|
1243
|
-
|
|
1278
|
+
if (at_template_literal) {
|
|
1279
|
+
if (this.curContext() !== q_tmpl) {
|
|
1280
|
+
this.context.push(q_tmpl);
|
|
1281
|
+
}
|
|
1282
|
+
} else {
|
|
1283
|
+
this.context = previous_context.filter(
|
|
1284
|
+
(context) =>
|
|
1285
|
+
context !== tstc.tc_expr && context !== tstc.tc_oTag && context !== tstc.tc_cTag,
|
|
1286
|
+
);
|
|
1287
|
+
if (this.curContext() !== b_stat) {
|
|
1288
|
+
this.context.push(b_stat);
|
|
1289
|
+
pushed_statement_context = true;
|
|
1290
|
+
}
|
|
1244
1291
|
}
|
|
1245
1292
|
this.exprAllowed = true;
|
|
1246
1293
|
const previous_path = this.#path;
|
|
@@ -1248,22 +1295,7 @@ export function TSRXPlugin(config) {
|
|
|
1248
1295
|
this.#templateScriptParsingDepth++;
|
|
1249
1296
|
let node;
|
|
1250
1297
|
try {
|
|
1251
|
-
// A code-block/directive body is statements plus at most one render node —
|
|
1252
|
-
// never bare text or markup tokens. If the tokenizer mis-read trailing
|
|
1253
|
-
// code as JSX (raw text or a tag-name token — both can happen for a
|
|
1254
|
-
// statement following the render node, depending on the leftover context),
|
|
1255
|
-
// reposition to the token start and re-read it as code now that the
|
|
1256
|
-
// template path is hidden. It then parses as a statement so the
|
|
1257
|
-
// one-render-node rule reports a clear "statements cannot follow" error
|
|
1258
|
-
// instead of a generic parse fault.
|
|
1259
1298
|
if (this.type === tstt.jsxText || this.type === tstt.jsxName) {
|
|
1260
|
-
// Rewinding `pos` to the mis-read token's start must also rewind the
|
|
1261
|
-
// line counter: a `jsxText` token can span newlines (e.g. the blank
|
|
1262
|
-
// line before a following render node), and reading it already
|
|
1263
|
-
// advanced `curLine`/`lineStart` to its end. Resetting only `pos`
|
|
1264
|
-
// would leave the line counter ahead of `pos`, inflating the `loc`
|
|
1265
|
-
// of this statement and every node after it (which crashes source-map
|
|
1266
|
-
// mapping when the inflated end line runs past the file).
|
|
1267
1299
|
const loc = acorn.getLineInfo(this.input, this.start);
|
|
1268
1300
|
this.pos = this.start;
|
|
1269
1301
|
this.curLine = loc.line;
|
|
@@ -1277,7 +1309,9 @@ export function TSRXPlugin(config) {
|
|
|
1277
1309
|
if (pushed_statement_context && this.curContext() === b_stat) {
|
|
1278
1310
|
this.context.pop();
|
|
1279
1311
|
}
|
|
1280
|
-
|
|
1312
|
+
if (!at_template_literal) {
|
|
1313
|
+
this.context = previous_context;
|
|
1314
|
+
}
|
|
1281
1315
|
}
|
|
1282
1316
|
if (this.curContext() === tstc.tc_expr) {
|
|
1283
1317
|
this.context.pop();
|
|
@@ -4344,22 +4378,29 @@ export function TSRXPlugin(config) {
|
|
|
4344
4378
|
parseTemplateBody(body) {
|
|
4345
4379
|
const current_template_node = this.#currentNativeTemplateNode();
|
|
4346
4380
|
if (!current_template_node) return;
|
|
4347
|
-
// Outside a `@{ … }` block every element/fragment body is plain JSX (§2,
|
|
4348
|
-
// §5). There is no script section and no `---` fence to infer — text is
|
|
4349
|
-
// text, and setup code lives only inside a code block.
|
|
4350
4381
|
current_template_node.metadata ??= { path: [] };
|
|
4351
4382
|
current_template_node.metadata.templateMode = 'template';
|
|
4352
4383
|
|
|
4353
|
-
// `@{ … }` code block as element/fragment content (§2 rule 1). Sibling
|
|
4354
|
-
// code blocks are allowed, so this is not gated on an empty body;
|
|
4355
|
-
// reposition onto the `@` if leading whitespace was tokenized ahead of it.
|
|
4356
4384
|
if (this.#atCodeBlockStart()) {
|
|
4357
4385
|
const at_index = skip_whitespace_from(this.input, this.start);
|
|
4358
4386
|
if (this.start !== at_index) {
|
|
4387
|
+
const ws_start = this.start;
|
|
4388
|
+
const ws_start_loc = this.startLoc;
|
|
4389
|
+
const ws_value = this.input.slice(ws_start, at_index);
|
|
4390
|
+
const text_node = /** @type {ESTreeJSX.JSXText} */ (
|
|
4391
|
+
this.startNodeAt(ws_start, ws_start_loc)
|
|
4392
|
+
);
|
|
4393
|
+
text_node.value = ws_value;
|
|
4394
|
+
text_node.raw = ws_value;
|
|
4359
4395
|
const loc = acorn.getLineInfo(this.input, at_index);
|
|
4396
|
+
const at_position = new acorn.Position(loc.line, loc.column);
|
|
4397
|
+
this.finishNodeAt(text_node, 'JSXText', at_index, at_position);
|
|
4398
|
+
if (this.#shouldKeepTemplateTextNode(text_node)) {
|
|
4399
|
+
body.push(text_node);
|
|
4400
|
+
}
|
|
4360
4401
|
this.pos = at_index;
|
|
4361
4402
|
this.start = at_index;
|
|
4362
|
-
this.startLoc =
|
|
4403
|
+
this.startLoc = at_position;
|
|
4363
4404
|
this.curLine = loc.line;
|
|
4364
4405
|
this.lineStart = at_index - loc.column;
|
|
4365
4406
|
}
|
|
@@ -4377,8 +4418,23 @@ export function TSRXPlugin(config) {
|
|
|
4377
4418
|
// text never starts at `<`, so drop the leaked context and re-read the
|
|
4378
4419
|
// tag instead of emitting an empty node.
|
|
4379
4420
|
if (this.input.charCodeAt(this.start) === CharCode.lessThan) {
|
|
4380
|
-
|
|
4381
|
-
this.
|
|
4421
|
+
if (this.input.charCodeAt(this.start + 1) === CharCode.slash) {
|
|
4422
|
+
while (this.curContext() === tstc.tc_expr) {
|
|
4423
|
+
this.context.pop();
|
|
4424
|
+
}
|
|
4425
|
+
} else {
|
|
4426
|
+
let native_depth = 0;
|
|
4427
|
+
for (const node of this.#path) {
|
|
4428
|
+
if (this.#isNativeTemplateNode(node)) native_depth++;
|
|
4429
|
+
}
|
|
4430
|
+
let tc_expr_depth = 0;
|
|
4431
|
+
for (const context of this.context) {
|
|
4432
|
+
if (context === tstc.tc_expr) tc_expr_depth++;
|
|
4433
|
+
}
|
|
4434
|
+
while (tc_expr_depth > native_depth && this.curContext() === tstc.tc_expr) {
|
|
4435
|
+
this.context.pop();
|
|
4436
|
+
tc_expr_depth--;
|
|
4437
|
+
}
|
|
4382
4438
|
}
|
|
4383
4439
|
this.pos = this.start;
|
|
4384
4440
|
this.exprAllowed = true;
|
|
@@ -4430,50 +4486,58 @@ export function TSRXPlugin(config) {
|
|
|
4430
4486
|
this.context.pop();
|
|
4431
4487
|
}
|
|
4432
4488
|
return;
|
|
4433
|
-
} else if (
|
|
4434
|
-
this.type === tstt.jsxTagStart ||
|
|
4435
|
-
this.input.charCodeAt(this.start) === CharCode.lessThan
|
|
4436
|
-
) {
|
|
4489
|
+
} else if (this.type === tstt.jsxTagStart) {
|
|
4437
4490
|
const startPos = this.start;
|
|
4438
4491
|
const startLoc = this.startLoc;
|
|
4439
|
-
|
|
4440
|
-
this.next();
|
|
4441
|
-
} else {
|
|
4442
|
-
// A control-flow block inside a native template can leave the tokenizer
|
|
4443
|
-
// in normal JS mode, so a closing tag may arrive as a relational
|
|
4444
|
-
// `<` token. Re-enter JSX closing-tag parsing manually.
|
|
4445
|
-
this.pos = startPos + 1;
|
|
4446
|
-
this.type = tstt.jsxTagStart;
|
|
4447
|
-
this.start = startPos;
|
|
4448
|
-
this.startLoc = startLoc;
|
|
4449
|
-
this.exprAllowed = false;
|
|
4450
|
-
// A genuine `jsxTagStart` pushes `tc_expr` + `tc_oTag` in its
|
|
4451
|
-
// `updateContext`; faking the token here skips those pushes. That is
|
|
4452
|
-
// harmless for an opening tag (the next token is the tag name), but a
|
|
4453
|
-
// closing tag (`</`) immediately runs `context.length -= 2` in the
|
|
4454
|
-
// slash `updateContext`, which would underflow the context stack and
|
|
4455
|
-
// throw "Invalid array length" (e.g. `<>@if (a) { … } done</>`). Push
|
|
4456
|
-
// the two contexts a real `jsxTagStart` would have added so the closing
|
|
4457
|
-
// tag pops its own contexts instead of the enclosing template's.
|
|
4458
|
-
if (this.input.charCodeAt(this.pos) === CharCode.slash) {
|
|
4459
|
-
this.context.push(tstc.tc_expr);
|
|
4460
|
-
this.context.push(tstc.tc_oTag);
|
|
4461
|
-
}
|
|
4462
|
-
this.next();
|
|
4463
|
-
}
|
|
4492
|
+
this.next();
|
|
4464
4493
|
if (this.value === '/' || this.type === tt.slash) {
|
|
4465
4494
|
// Consume '/'
|
|
4466
4495
|
this.next();
|
|
4467
4496
|
|
|
4468
|
-
|
|
4497
|
+
const closingNode = this.startNodeAt(startPos, startLoc);
|
|
4498
|
+
const inside_parent_template =
|
|
4499
|
+
this.#jsxExpressionContainerDepth === 0 &&
|
|
4500
|
+
this.#templateScriptParsingDepth === 0 &&
|
|
4501
|
+
this.#path.slice(0, -1).some((node) => this.#isNativeTemplateNode(node));
|
|
4469
4502
|
this.#closingNativeTemplateNode = true;
|
|
4503
|
+
/** @type {ReturnType<Parse.Parser['jsx_parseElementName']>} */
|
|
4504
|
+
let closingName;
|
|
4470
4505
|
try {
|
|
4471
|
-
|
|
4472
|
-
this.jsx_parseClosingElementAt(startPos, startLoc)
|
|
4473
|
-
);
|
|
4506
|
+
closingName = this.jsx_parseElementName();
|
|
4474
4507
|
} finally {
|
|
4475
4508
|
this.#closingNativeTemplateNode = false;
|
|
4476
4509
|
}
|
|
4510
|
+
if (closingName) {
|
|
4511
|
+
/** @type {ESTreeJSX.JSXClosingElement} */ (closingNode).name =
|
|
4512
|
+
/** @type {ESTreeJSX.JSXIdentifier} */ (closingName);
|
|
4513
|
+
}
|
|
4514
|
+
const current = /** @type {ESTreeJSX.JSXFragment | ESTreeJSX.JSXElement} */ (
|
|
4515
|
+
this.#path[this.#path.length - 1]
|
|
4516
|
+
);
|
|
4517
|
+
const current_name = this.#isNativeTemplateNode(current)
|
|
4518
|
+
? current.type === 'JSXFragment'
|
|
4519
|
+
? ''
|
|
4520
|
+
: current.openingElement?.name
|
|
4521
|
+
? this.getElementName(current.openingElement.name)
|
|
4522
|
+
: null
|
|
4523
|
+
: null;
|
|
4524
|
+
const closing_name_str = !closingName
|
|
4525
|
+
? ''
|
|
4526
|
+
: closingName.type === 'JSXNamespacedName'
|
|
4527
|
+
? closingName.namespace.name + ':' + closingName.name.name
|
|
4528
|
+
: this.getElementName(closingName);
|
|
4529
|
+
if (!(inside_parent_template && current_name === closing_name_str)) {
|
|
4530
|
+
this.#closingNativeTemplateNode = true;
|
|
4531
|
+
}
|
|
4532
|
+
this.expect(tstt.jsxTagEnd);
|
|
4533
|
+
this.#closingNativeTemplateNode = false;
|
|
4534
|
+
const closingElement =
|
|
4535
|
+
/** @type {ESTreeJSX.JSXClosingElement & AST.NodeWithLocation} */ (
|
|
4536
|
+
this.finishNode(
|
|
4537
|
+
closingNode,
|
|
4538
|
+
closingName ? 'JSXClosingElement' : 'JSXClosingFragment',
|
|
4539
|
+
)
|
|
4540
|
+
);
|
|
4477
4541
|
if (this.#isDynamicJSXElementName(closingElement.name)) {
|
|
4478
4542
|
/** @type {any} */ (closingElement).isDynamic = true;
|
|
4479
4543
|
}
|
|
@@ -4586,7 +4650,7 @@ export function TSRXPlugin(config) {
|
|
|
4586
4650
|
}
|
|
4587
4651
|
|
|
4588
4652
|
this.#path.pop();
|
|
4589
|
-
|
|
4653
|
+
this.#skipTrailingLayoutWhitespace();
|
|
4590
4654
|
return;
|
|
4591
4655
|
}
|
|
4592
4656
|
const node = this.parseElement();
|
|
@@ -19,6 +19,20 @@ export function in_jsx_child_context(path) {
|
|
|
19
19
|
return !!parent && (parent.type === 'JSXElement' || parent.type === 'JSXFragment');
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* @param {any} node
|
|
24
|
+
* @returns {boolean}
|
|
25
|
+
*/
|
|
26
|
+
export function is_empty_jsx_fragment(node) {
|
|
27
|
+
return (
|
|
28
|
+
node?.type === 'JSXFragment' &&
|
|
29
|
+
!(node.children || []).some(
|
|
30
|
+
(/** @type {any} */ child) =>
|
|
31
|
+
child && (child.type !== 'JSXText' || child.value.trim() !== ''),
|
|
32
|
+
)
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
22
36
|
/**
|
|
23
37
|
* Match Ripple's transform path metadata shape: every node seen by the walker
|
|
24
38
|
* carries its current ancestor path for downstream CSS pruning and mapping
|
|
@@ -36,45 +50,6 @@ export function set_node_path_metadata(node, path) {
|
|
|
36
50
|
}
|
|
37
51
|
}
|
|
38
52
|
|
|
39
|
-
/**
|
|
40
|
-
* Flatten a JSX-compatible island's children into a single expression. In a
|
|
41
|
-
* JSX-child position, a JSXExpressionContainer `{expr}` is valid and must stay
|
|
42
|
-
* wrapped. In an expression position (e.g. `return ...`), `{expr}` parses as
|
|
43
|
-
* a block/object literal, so unwrap to `expr`.
|
|
44
|
-
*
|
|
45
|
-
* @param {any} node
|
|
46
|
-
* @param {boolean} [in_jsx_child]
|
|
47
|
-
* @returns {any}
|
|
48
|
-
*/
|
|
49
|
-
export function tsx_node_to_jsx_expression(node, in_jsx_child = false) {
|
|
50
|
-
const children = (node.children || []).filter(
|
|
51
|
-
(/** @type {any} */ child) => child.type !== 'JSXText' || child.value.trim() !== '',
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
-
children.length === 1 &&
|
|
56
|
-
children[0].type !== 'JSXText' &&
|
|
57
|
-
// Reactive-block containers (dynamic tags) must stay expression
|
|
58
|
-
// children so the host JSX compiler wraps them in a render block;
|
|
59
|
-
// unwrapping to a bare call would evaluate them once.
|
|
60
|
-
children[0].metadata?.tsrx_reactive_block !== true
|
|
61
|
-
) {
|
|
62
|
-
const only = children[0];
|
|
63
|
-
if (only.type === 'JSXExpressionContainer' && !in_jsx_child) {
|
|
64
|
-
return only.expression;
|
|
65
|
-
}
|
|
66
|
-
return only;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return /** @type {any} */ ({
|
|
70
|
-
type: 'JSXFragment',
|
|
71
|
-
openingFragment: { type: 'JSXOpeningFragment', metadata: { path: [] } },
|
|
72
|
-
closingFragment: { type: 'JSXClosingFragment', metadata: { path: [] } },
|
|
73
|
-
children,
|
|
74
|
-
metadata: { path: [] },
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
53
|
/**
|
|
79
54
|
* Wrap esrap's `tsx()` printer with location markers for nodes whose spans
|
|
80
55
|
* (e.g. the leading `new ` of a NewExpression or the angle-bracket delimiters
|