@tsrx/solid 0.1.4 → 0.1.7
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/index.js +1 -0
- package/src/transform.js +222 -318
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Solid compiler built on @tsrx/core",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Dominic Gannaway",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.7",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"publishConfig": {
|
|
9
9
|
"access": "public"
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"esrap": "^2.
|
|
27
|
+
"esrap": "^2.2.7",
|
|
28
28
|
"zimmerframe": "^1.1.2",
|
|
29
|
-
"@tsrx/core": "0.1.
|
|
29
|
+
"@tsrx/core": "0.1.7"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"solid-js": ">=1.8 || >=2.0.0-beta"
|
package/src/index.js
CHANGED
package/src/transform.js
CHANGED
|
@@ -11,9 +11,7 @@ import {
|
|
|
11
11
|
setLocation,
|
|
12
12
|
addJsxSetupDeclaration as add_jsx_setup_declaration,
|
|
13
13
|
applyLazyTransforms as apply_lazy_transforms,
|
|
14
|
-
collectLazyBindingsFromComponent as collect_lazy_bindings_from_component,
|
|
15
14
|
extractJsxSetupDeclarations as extract_jsx_setup_declarations,
|
|
16
|
-
replaceLazyParams as replace_lazy_params,
|
|
17
15
|
rewriteLoopContinuesToBareReturns as rewrite_loop_continues_to_bare_returns,
|
|
18
16
|
isRefPropExpression as is_ref_prop_expression,
|
|
19
17
|
isInterleavedBody as is_interleaved_body_core,
|
|
@@ -27,10 +25,14 @@ import {
|
|
|
27
25
|
clone_expression_node,
|
|
28
26
|
clone_identifier,
|
|
29
27
|
clone_jsx_name,
|
|
28
|
+
cloneSwitchHelperInvocation as clone_switch_helper_invocation,
|
|
29
|
+
collectParamBindings as collect_param_bindings,
|
|
30
|
+
collectStatementBindings as collect_statement_bindings,
|
|
31
|
+
contains_component_jsx,
|
|
30
32
|
create_generated_identifier,
|
|
31
33
|
create_null_literal,
|
|
32
|
-
flatten_switch_consequent,
|
|
33
34
|
get_for_of_iteration_params,
|
|
35
|
+
planSwitchLift as plan_switch_lift,
|
|
34
36
|
identifier_to_jsx_name,
|
|
35
37
|
is_bare_render_expression,
|
|
36
38
|
is_dynamic_element_id,
|
|
@@ -104,6 +106,12 @@ const solid_platform = {
|
|
|
104
106
|
scanUseServerDirectiveForAwaitWithCustomValidator: false,
|
|
105
107
|
},
|
|
106
108
|
hooks: {
|
|
109
|
+
// Hoist to module scope in the client transform —
|
|
110
|
+
// same trade-off as React and Vue, where one definition per helper
|
|
111
|
+
// keeps bundles small and source mappings 1:1. The
|
|
112
|
+
// `compile_to_volar_mappings` entry point opts back out so Volar's
|
|
113
|
+
// type-only output keeps helpers inline against the component body.
|
|
114
|
+
moduleScopedHookComponents: true,
|
|
107
115
|
initialState: () => ({
|
|
108
116
|
needs_show: false,
|
|
109
117
|
needs_for: false,
|
|
@@ -113,6 +121,17 @@ const solid_platform = {
|
|
|
113
121
|
needs_loading: false,
|
|
114
122
|
needs_normalize_spread_props: false,
|
|
115
123
|
}),
|
|
124
|
+
canHoistStaticNode(node) {
|
|
125
|
+
// Solid's reactive runtime doesn't reuse JSX-element identity the
|
|
126
|
+
// way React does, so hoisting `<Component />` references to module
|
|
127
|
+
// level pays no runtime cost — it just creates an extra `const`
|
|
128
|
+
// that aliases a helper invocation (e.g. `App__static1 =
|
|
129
|
+
// <App__StatementBodyHook2 />`). Truly-static DOM trees like
|
|
130
|
+
// `<span>'Hello'</span>` still benefit from being hoisted out of
|
|
131
|
+
// the per-render closure, so we only veto hoisting when the
|
|
132
|
+
// subtree contains a *component* JSX element. Same logic Vue uses.
|
|
133
|
+
return !contains_component_jsx(node);
|
|
134
|
+
},
|
|
116
135
|
validateComponentAwait: (await_expression, _component, ctx, _requires, source) => {
|
|
117
136
|
const await_start = get_await_keyword_start(await_expression, source);
|
|
118
137
|
const adjusted_node = /** @type {any} */ ({
|
|
@@ -134,8 +153,8 @@ const solid_platform = {
|
|
|
134
153
|
switchStatement: switch_statement_to_jsx_child,
|
|
135
154
|
tryStatement: try_statement_to_jsx_child,
|
|
136
155
|
},
|
|
137
|
-
componentToFunction: (component, ctx) =>
|
|
138
|
-
component_to_function_declaration(component, /** @type {any} */ (ctx)),
|
|
156
|
+
componentToFunction: (component, ctx, helper_state) =>
|
|
157
|
+
component_to_function_declaration(component, /** @type {any} */ (ctx), helper_state),
|
|
139
158
|
injectImports: (program, ctx) => inject_solid_imports(program, /** @type {any} */ (ctx)),
|
|
140
159
|
// `transformElementAttributes` is intentionally omitted: the
|
|
141
160
|
// `transformElement` hook below short-circuits core's element walker
|
|
@@ -180,17 +199,41 @@ function get_await_keyword_start(await_node, source) {
|
|
|
180
199
|
/**
|
|
181
200
|
* @param {any} component
|
|
182
201
|
* @param {TransformContext} transform_context
|
|
202
|
+
* @param {any} [walk_helper_state]
|
|
203
|
+
* The helper_state owned by the walker for this component. Solid's local
|
|
204
|
+
* body lowering happens *after* the walker has restored `helper_state` to
|
|
205
|
+
* the outer scope's value, so we re-install the walker's helper_state here
|
|
206
|
+
* for the duration of the body lowering. That way `to_jsx_child` calls into
|
|
207
|
+
* `switch_statement_to_jsx_child` (and any other lift path that goes
|
|
208
|
+
* through `create_hook_safe_helper`) see the same module-scoped helper
|
|
209
|
+
* bucket the React / Vue paths see, and `moduleScopedHookComponents: true`
|
|
210
|
+
* actually hoists Solid's `<StatementBodyHook/>` helpers out of the
|
|
211
|
+
* component body.
|
|
183
212
|
* @returns {AST.FunctionDeclaration | AST.FunctionExpression | AST.ArrowFunctionExpression}
|
|
184
213
|
*/
|
|
185
|
-
function component_to_function_declaration(component, transform_context) {
|
|
214
|
+
function component_to_function_declaration(component, transform_context, walk_helper_state) {
|
|
186
215
|
const params = component.params || [];
|
|
187
216
|
const body = /** @type {any[]} */ (component.body || []);
|
|
188
217
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
218
|
+
const saved_helper_state = transform_context.helper_state;
|
|
219
|
+
if (walk_helper_state) {
|
|
220
|
+
transform_context.helper_state = walk_helper_state;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Re-install the component's body bindings before lowering the body. The
|
|
224
|
+
// walker sets `state.available_bindings` to the component-scope bindings
|
|
225
|
+
// during `next()` and restores them before calling the platform's
|
|
226
|
+
// `componentToFunction` hook — but Solid's body lowering happens *here*,
|
|
227
|
+
// after that restore, so `to_jsx_child` (and any `create_hook_safe_helper`
|
|
228
|
+
// it triggers via `switch_statement_to_jsx_child` / the if / try / for-of
|
|
229
|
+
// lifts) wouldn't otherwise see component-scope locals like `const obj =
|
|
230
|
+
// {...}` and would emit helpers that close over undefined references.
|
|
231
|
+
const saved_bindings = transform_context.available_bindings;
|
|
232
|
+
const body_bindings = collect_param_bindings(params);
|
|
233
|
+
for (const node of body) {
|
|
234
|
+
collect_statement_bindings(node, body_bindings);
|
|
235
|
+
}
|
|
236
|
+
transform_context.available_bindings = body_bindings;
|
|
194
237
|
|
|
195
238
|
// Detect top-level early-return patterns such as `if (cond) { return; }`
|
|
196
239
|
// and `if (cond) { <p />; return; }`.
|
|
@@ -314,75 +357,38 @@ function component_to_function_declaration(component, transform_context) {
|
|
|
314
357
|
}
|
|
315
358
|
|
|
316
359
|
if (render_nodes.length > 0) {
|
|
317
|
-
statements.push(
|
|
318
|
-
/** @type {any} */ ({
|
|
319
|
-
type: 'ReturnStatement',
|
|
320
|
-
argument: build_return_expression(render_nodes) || {
|
|
321
|
-
type: 'Literal',
|
|
322
|
-
value: null,
|
|
323
|
-
raw: 'null',
|
|
324
|
-
metadata: { path: [] },
|
|
325
|
-
},
|
|
326
|
-
metadata: { path: [] },
|
|
327
|
-
}),
|
|
328
|
-
);
|
|
360
|
+
statements.push(b.return(build_return_expression(render_nodes) || b.literal(null)));
|
|
329
361
|
}
|
|
330
362
|
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
const body_block = /** @type {any} */ ({
|
|
334
|
-
type: 'BlockStatement',
|
|
335
|
-
body: statements,
|
|
336
|
-
metadata: { path: [] },
|
|
337
|
-
});
|
|
338
|
-
const final_body =
|
|
339
|
-
lazy_bindings.size > 0 ? apply_lazy_transforms(body_block, lazy_bindings) : body_block;
|
|
363
|
+
const body_block = b.block(statements);
|
|
340
364
|
|
|
341
365
|
/** @type {AST.FunctionDeclaration | AST.FunctionExpression | AST.ArrowFunctionExpression} */
|
|
342
366
|
let fn;
|
|
343
367
|
|
|
344
368
|
if (component.id) {
|
|
345
|
-
fn =
|
|
346
|
-
type: 'FunctionDeclaration',
|
|
347
|
-
id: component.id,
|
|
348
|
-
typeParameters: component.typeParameters,
|
|
349
|
-
params: final_params,
|
|
350
|
-
body: final_body,
|
|
351
|
-
async: false,
|
|
352
|
-
generator: false,
|
|
353
|
-
metadata: {
|
|
354
|
-
path: [],
|
|
355
|
-
is_component: true,
|
|
356
|
-
},
|
|
357
|
-
});
|
|
369
|
+
fn = b.function_declaration(component.id, params, body_block, false, component.typeParameters);
|
|
358
370
|
} else if (component.metadata?.arrow) {
|
|
359
|
-
fn =
|
|
360
|
-
type: 'ArrowFunctionExpression',
|
|
361
|
-
typeParameters: component.typeParameters,
|
|
362
|
-
params: final_params,
|
|
363
|
-
body: final_body,
|
|
364
|
-
async: false,
|
|
365
|
-
generator: false,
|
|
366
|
-
expression: false,
|
|
367
|
-
metadata: {
|
|
368
|
-
path: [],
|
|
369
|
-
is_component: true,
|
|
370
|
-
},
|
|
371
|
-
});
|
|
371
|
+
fn = b.arrow(params, body_block, false, component.typeParameters);
|
|
372
372
|
} else {
|
|
373
|
-
fn =
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
373
|
+
fn = b.function(null, params, body_block, false, component.typeParameters);
|
|
374
|
+
}
|
|
375
|
+
fn.metadata.is_component = true;
|
|
376
|
+
|
|
377
|
+
// `preallocate_lazy_ids` stamped `has_lazy_descendants` on the source
|
|
378
|
+
// `Component` node; the freshly-built `fn` shares the same params/body
|
|
379
|
+
// subtree, so propagate the flag so the function-handler's early-return
|
|
380
|
+
// path can fire for non-lazy components.
|
|
381
|
+
if (/** @type {any} */ (component).metadata?.has_lazy_descendants) {
|
|
382
|
+
/** @type {any} */ (fn.metadata).has_lazy_descendants = true;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Apply lazy `&{}` / `&[]` rewrites end-to-end via the function-handler in
|
|
386
|
+
// `apply_lazy_transforms`. Constant-time fast-path for functions whose
|
|
387
|
+
// subtrees contain no lazy patterns (flagged ahead of time by
|
|
388
|
+
// `preallocate_lazy_ids`). In type-only mode the rewrite is skipped so
|
|
389
|
+
// destructuring patterns survive into the virtual TSX.
|
|
390
|
+
if (!transform_context.typeOnly) {
|
|
391
|
+
fn = /** @type {typeof fn} */ (apply_lazy_transforms(fn, new Map()));
|
|
386
392
|
}
|
|
387
393
|
|
|
388
394
|
if (fn.type === 'FunctionDeclaration' && fn.id) {
|
|
@@ -393,6 +399,20 @@ function component_to_function_declaration(component, transform_context) {
|
|
|
393
399
|
}
|
|
394
400
|
|
|
395
401
|
setLocation(fn, /** @type {any} */ (component), true);
|
|
402
|
+
|
|
403
|
+
// Restore the outer helper_state and bindings, then surface this
|
|
404
|
+
// component's generated helpers / statics so the downstream Program-level
|
|
405
|
+
// lift can hoist them (`moduleScopedHookComponents: true` registers
|
|
406
|
+
// helpers into `helper_state.helpers`; the React/Vue post-pass reads them
|
|
407
|
+
// off `fn.metadata.generated_helpers`).
|
|
408
|
+
transform_context.helper_state = saved_helper_state;
|
|
409
|
+
transform_context.available_bindings = saved_bindings;
|
|
410
|
+
if (walk_helper_state) {
|
|
411
|
+
const fn_metadata = /** @type {any} */ (fn.metadata);
|
|
412
|
+
fn_metadata.generated_helpers = walk_helper_state.helpers;
|
|
413
|
+
fn_metadata.generated_statics = walk_helper_state.statics;
|
|
414
|
+
}
|
|
415
|
+
|
|
396
416
|
return fn;
|
|
397
417
|
}
|
|
398
418
|
|
|
@@ -528,14 +548,12 @@ function body_to_jsx_child(body_nodes, transform_context) {
|
|
|
528
548
|
let capture_index = 0;
|
|
529
549
|
for (const child of body_nodes) {
|
|
530
550
|
if (is_bare_return_statement(child)) {
|
|
531
|
-
statements.push(
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
loc: child.loc,
|
|
538
|
-
});
|
|
551
|
+
statements.push(
|
|
552
|
+
set_loc(
|
|
553
|
+
b.return(children.length > 0 ? build_return_expression(children) : create_null_literal()),
|
|
554
|
+
child,
|
|
555
|
+
),
|
|
556
|
+
);
|
|
539
557
|
children.length = 0;
|
|
540
558
|
has_terminal_return = true;
|
|
541
559
|
continue;
|
|
@@ -580,27 +598,13 @@ function body_to_jsx_child(body_nodes, transform_context) {
|
|
|
580
598
|
const block_body = [...statements];
|
|
581
599
|
if (children.length > 0 || !has_terminal_return) {
|
|
582
600
|
block_body.push(
|
|
583
|
-
|
|
584
|
-
type: 'ReturnStatement',
|
|
585
|
-
argument: children.length > 0 ? build_return_expression(children) : create_null_literal(),
|
|
586
|
-
metadata: { path: [] },
|
|
587
|
-
}),
|
|
601
|
+
b.return(children.length > 0 ? build_return_expression(children) : create_null_literal()),
|
|
588
602
|
);
|
|
589
603
|
}
|
|
590
604
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
body: {
|
|
595
|
-
type: 'BlockStatement',
|
|
596
|
-
body: block_body,
|
|
597
|
-
metadata: { path: [] },
|
|
598
|
-
},
|
|
599
|
-
async: false,
|
|
600
|
-
generator: false,
|
|
601
|
-
expression: false,
|
|
602
|
-
metadata: { path: [], is_branch_arrow: true },
|
|
603
|
-
});
|
|
605
|
+
const arrow = b.arrow([], b.block(block_body));
|
|
606
|
+
/** @type {any} */ (arrow.metadata).is_branch_arrow = true;
|
|
607
|
+
return arrow;
|
|
604
608
|
}
|
|
605
609
|
|
|
606
610
|
/**
|
|
@@ -686,14 +690,7 @@ function loop_body_to_callback_statements(body_nodes, transform_context) {
|
|
|
686
690
|
const create_return_statement = (source_node, render_nodes) => {
|
|
687
691
|
const cloned = render_nodes.map((node) => clone_expression_node(node));
|
|
688
692
|
const argument = cloned.length > 0 ? build_return_expression(cloned) : create_null_literal();
|
|
689
|
-
return
|
|
690
|
-
type: 'ReturnStatement',
|
|
691
|
-
argument,
|
|
692
|
-
metadata: { path: [] },
|
|
693
|
-
start: source_node?.start,
|
|
694
|
-
end: source_node?.end,
|
|
695
|
-
loc: source_node?.loc,
|
|
696
|
-
};
|
|
693
|
+
return set_loc(b.return(argument), source_node);
|
|
697
694
|
};
|
|
698
695
|
|
|
699
696
|
/** @param {any} source_node */
|
|
@@ -719,20 +716,7 @@ function loop_body_to_callback_statements(body_nodes, transform_context) {
|
|
|
719
716
|
transform_context,
|
|
720
717
|
);
|
|
721
718
|
prepend_render_nodes_to_return_statements(branch_statements, children);
|
|
722
|
-
statements.push(
|
|
723
|
-
type: 'IfStatement',
|
|
724
|
-
test: child.test,
|
|
725
|
-
consequent: {
|
|
726
|
-
type: 'BlockStatement',
|
|
727
|
-
body: branch_statements,
|
|
728
|
-
metadata: { path: [] },
|
|
729
|
-
},
|
|
730
|
-
alternate: null,
|
|
731
|
-
metadata: { path: [] },
|
|
732
|
-
start: child.start,
|
|
733
|
-
end: child.end,
|
|
734
|
-
loc: child.loc,
|
|
735
|
-
});
|
|
719
|
+
statements.push(set_loc(b.if(child.test, b.block(branch_statements), null), child));
|
|
736
720
|
continue;
|
|
737
721
|
}
|
|
738
722
|
|
|
@@ -953,13 +937,7 @@ function negate_expression(expr) {
|
|
|
953
937
|
return clone_expression_node(expr.argument);
|
|
954
938
|
}
|
|
955
939
|
|
|
956
|
-
return
|
|
957
|
-
type: 'UnaryExpression',
|
|
958
|
-
operator: '!',
|
|
959
|
-
prefix: true,
|
|
960
|
-
argument: clone_expression_node(expr),
|
|
961
|
-
metadata: { path: [] },
|
|
962
|
-
};
|
|
940
|
+
return b.unary('!', clone_expression_node(expr));
|
|
963
941
|
}
|
|
964
942
|
|
|
965
943
|
/**
|
|
@@ -971,13 +949,7 @@ function negate_expression(expr) {
|
|
|
971
949
|
*/
|
|
972
950
|
function iife_if_arrow(node) {
|
|
973
951
|
if (!is_branch_arrow(node)) return node;
|
|
974
|
-
return
|
|
975
|
-
type: 'CallExpression',
|
|
976
|
-
callee: node,
|
|
977
|
-
arguments: [],
|
|
978
|
-
optional: false,
|
|
979
|
-
metadata: { path: [] },
|
|
980
|
-
};
|
|
952
|
+
return b.call(node);
|
|
981
953
|
}
|
|
982
954
|
|
|
983
955
|
/**
|
|
@@ -1137,54 +1109,26 @@ function for_of_statement_to_jsx_child(node, transform_context) {
|
|
|
1137
1109
|
)
|
|
1138
1110
|
);
|
|
1139
1111
|
|
|
1140
|
-
let arrow
|
|
1141
|
-
type: 'ArrowFunctionExpression',
|
|
1142
|
-
params: loop_params,
|
|
1143
|
-
body: null,
|
|
1144
|
-
async: false,
|
|
1145
|
-
generator: false,
|
|
1146
|
-
expression: true,
|
|
1147
|
-
metadata: { path: [] },
|
|
1148
|
-
});
|
|
1112
|
+
let arrow;
|
|
1149
1113
|
|
|
1150
1114
|
if (body_has_loop_skip(loop_body)) {
|
|
1151
|
-
arrow
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
};
|
|
1156
|
-
arrow.expression = false;
|
|
1115
|
+
arrow = b.arrow(
|
|
1116
|
+
loop_params,
|
|
1117
|
+
b.block(loop_body_to_callback_statements(loop_body, transform_context)),
|
|
1118
|
+
);
|
|
1157
1119
|
} else {
|
|
1120
|
+
// Placeholder body — merge_branch_body_into_arrow replaces it below.
|
|
1121
|
+
arrow = b.arrow(loop_params, b.literal(null));
|
|
1158
1122
|
arrow = merge_branch_body_into_arrow(arrow, body_to_jsx_child(loop_body, transform_context));
|
|
1159
1123
|
}
|
|
1160
1124
|
|
|
1161
|
-
const attributes = [
|
|
1162
|
-
{
|
|
1163
|
-
type: 'JSXAttribute',
|
|
1164
|
-
name: { type: 'JSXIdentifier', name: 'each', metadata: { path: [] } },
|
|
1165
|
-
value: to_jsx_expression_container(node.right),
|
|
1166
|
-
metadata: { path: [] },
|
|
1167
|
-
},
|
|
1168
|
-
];
|
|
1125
|
+
const attributes = [b.jsx_attribute(b.jsx_id('each'), to_jsx_expression_container(node.right))];
|
|
1169
1126
|
|
|
1170
1127
|
if (node.key) {
|
|
1171
1128
|
const item_param = clone_expression_node(loop_params[0]);
|
|
1172
|
-
const keyed_arrow =
|
|
1173
|
-
type: 'ArrowFunctionExpression',
|
|
1174
|
-
params: [item_param],
|
|
1175
|
-
body: node.key,
|
|
1176
|
-
async: false,
|
|
1177
|
-
generator: false,
|
|
1178
|
-
expression: true,
|
|
1179
|
-
metadata: { path: [] },
|
|
1180
|
-
});
|
|
1129
|
+
const keyed_arrow = b.arrow([item_param], node.key);
|
|
1181
1130
|
attributes.push(
|
|
1182
|
-
|
|
1183
|
-
type: 'JSXAttribute',
|
|
1184
|
-
name: { type: 'JSXIdentifier', name: 'keyed', metadata: { path: [] } },
|
|
1185
|
-
value: to_jsx_expression_container(keyed_arrow, node.key),
|
|
1186
|
-
metadata: { path: [] },
|
|
1187
|
-
}),
|
|
1131
|
+
b.jsx_attribute(b.jsx_id('keyed'), to_jsx_expression_container(keyed_arrow, node.key)),
|
|
1188
1132
|
);
|
|
1189
1133
|
}
|
|
1190
1134
|
|
|
@@ -1197,6 +1141,20 @@ function for_of_statement_to_jsx_child(node, transform_context) {
|
|
|
1197
1141
|
* statement with a discriminant `d` and cases `[c1, c2, default]` becomes:
|
|
1198
1142
|
* <Switch fallback={...default}><Match when={d === c1}>...</Match>...</Switch>
|
|
1199
1143
|
*
|
|
1144
|
+
* Fall-through across cases reuses the shared `plan_switch_lift` pipeline
|
|
1145
|
+
* from `@tsrx/core`: each duplicated case body is hoisted into a
|
|
1146
|
+
* `StatementBodyHook` helper component that chains into the next helper, and
|
|
1147
|
+
* each `<Match>` body just renders the appropriate helper element. The
|
|
1148
|
+
* client transform hoists those helpers to module scope (Solid's platform
|
|
1149
|
+
* sets `moduleScopedHookComponents: true`); `compile_to_volar_mappings` opts
|
|
1150
|
+
* back out and emits the helpers locally inside the component body so Volar
|
|
1151
|
+
* still sees closure-captured bindings against the component scope.
|
|
1152
|
+
*
|
|
1153
|
+
* When any case is lifted in `typeOnly` mode the helper declarations have to
|
|
1154
|
+
* live somewhere local-scoped — we wrap the whole `<Switch>` in an IIFE that
|
|
1155
|
+
* declares them in order and returns the element. The client transform's
|
|
1156
|
+
* module-scoped helpers leave that IIFE empty, so we skip the wrapper.
|
|
1157
|
+
*
|
|
1200
1158
|
* @param {any} node
|
|
1201
1159
|
* @param {TransformContext} transform_context
|
|
1202
1160
|
* @returns {any}
|
|
@@ -1205,20 +1163,51 @@ function switch_statement_to_jsx_child(node, transform_context) {
|
|
|
1205
1163
|
transform_context.needs_switch = true;
|
|
1206
1164
|
transform_context.needs_match = true;
|
|
1207
1165
|
|
|
1166
|
+
const { case_info, case_helpers, find_next_helper_after, setup_statements } = plan_switch_lift(
|
|
1167
|
+
node,
|
|
1168
|
+
transform_context,
|
|
1169
|
+
);
|
|
1170
|
+
|
|
1208
1171
|
/** @type {any} */
|
|
1209
1172
|
let fallback = null;
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
const
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1173
|
+
/** @type {Array<{ test: any, body_jsx: any }>} */
|
|
1174
|
+
const match_entries = [];
|
|
1175
|
+
|
|
1176
|
+
for (let i = 0; i < node.cases.length; i++) {
|
|
1177
|
+
const original_case = node.cases[i];
|
|
1178
|
+
const info = case_info[i];
|
|
1179
|
+
const helper = case_helpers[i];
|
|
1180
|
+
|
|
1181
|
+
/** @type {any} */
|
|
1182
|
+
let body_jsx;
|
|
1183
|
+
if (helper) {
|
|
1184
|
+
// Lifted case: render the helper element directly. Use the
|
|
1185
|
+
// original `component_element` (not a clone) for this — its
|
|
1186
|
+
// definition's `loc` is what the case position should map to.
|
|
1187
|
+
body_jsx = helper.component_element;
|
|
1188
|
+
} else if (info.own_body.length === 0) {
|
|
1189
|
+
// Empty case in the source. If a downstream chain exists (alias
|
|
1190
|
+
// pattern like `case 1: case 2: body; break;`), the `<Match>` for
|
|
1191
|
+
// the empty label still has to render that downstream body —
|
|
1192
|
+
// Solid's `<Match>` arms are exclusive, so JS fall-through can't
|
|
1193
|
+
// rescue us here.
|
|
1194
|
+
const next_helper = find_next_helper_after(i);
|
|
1195
|
+
body_jsx = next_helper ? clone_switch_helper_invocation(next_helper) : create_null_literal();
|
|
1196
|
+
} else {
|
|
1197
|
+
// Inline case body: process JSX/non-JSX statements just like Solid
|
|
1198
|
+
// does for any other branch body, then append the chain helper if
|
|
1199
|
+
// this case falls through with no terminator.
|
|
1200
|
+
const inline_body = [...info.own_body];
|
|
1201
|
+
if (!info.has_terminator) {
|
|
1202
|
+
const next_helper = find_next_helper_after(i);
|
|
1203
|
+
if (next_helper) {
|
|
1204
|
+
inline_body.push(clone_switch_helper_invocation(next_helper));
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
body_jsx = body_to_jsx_child(inline_body, transform_context);
|
|
1218
1208
|
}
|
|
1219
1209
|
|
|
1220
|
-
|
|
1221
|
-
if (switch_case.test === null) {
|
|
1210
|
+
if (original_case.test === null) {
|
|
1222
1211
|
fallback = body_jsx;
|
|
1223
1212
|
continue;
|
|
1224
1213
|
}
|
|
@@ -1226,31 +1215,29 @@ function switch_statement_to_jsx_child(node, transform_context) {
|
|
|
1226
1215
|
// Clone the discriminant per-case: every generated `<Match when={d === caseN}>`
|
|
1227
1216
|
// would otherwise share the same AST node reference, so a downstream pass
|
|
1228
1217
|
// (lazy transforms, printer metadata, source-map annotation) mutating it on
|
|
1229
|
-
// one case would corrupt the others.
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
left: clone_expression_node(node.discriminant),
|
|
1234
|
-
right: switch_case.test,
|
|
1235
|
-
metadata: { path: [] },
|
|
1236
|
-
});
|
|
1218
|
+
// one case would corrupt the others. The right operand (`caseN`) is the
|
|
1219
|
+
// original source `test` node — unique per case, so we keep its real loc
|
|
1220
|
+
// for editor IntelliSense and don't clone it.
|
|
1221
|
+
const test = b.binary('===', clone_expression_node(node.discriminant), original_case.test);
|
|
1237
1222
|
|
|
1238
|
-
|
|
1239
|
-
create_jsx_element(
|
|
1240
|
-
'Match',
|
|
1241
|
-
[
|
|
1242
|
-
{
|
|
1243
|
-
type: 'JSXAttribute',
|
|
1244
|
-
name: { type: 'JSXIdentifier', name: 'when', metadata: { path: [] } },
|
|
1245
|
-
value: to_jsx_expression_container(test),
|
|
1246
|
-
metadata: { path: [] },
|
|
1247
|
-
},
|
|
1248
|
-
],
|
|
1249
|
-
[jsx_child_wrap(to_function_child(body_jsx))],
|
|
1250
|
-
),
|
|
1251
|
-
);
|
|
1223
|
+
match_entries.push({ test, body_jsx });
|
|
1252
1224
|
}
|
|
1253
1225
|
|
|
1226
|
+
const match_children = match_entries.map(({ test, body_jsx }) =>
|
|
1227
|
+
create_jsx_element(
|
|
1228
|
+
'Match',
|
|
1229
|
+
[
|
|
1230
|
+
{
|
|
1231
|
+
type: 'JSXAttribute',
|
|
1232
|
+
name: { type: 'JSXIdentifier', name: 'when', metadata: { path: [] } },
|
|
1233
|
+
value: to_jsx_expression_container(test),
|
|
1234
|
+
metadata: { path: [] },
|
|
1235
|
+
},
|
|
1236
|
+
],
|
|
1237
|
+
[jsx_child_wrap(to_function_child(body_jsx))],
|
|
1238
|
+
),
|
|
1239
|
+
);
|
|
1240
|
+
|
|
1254
1241
|
const attributes =
|
|
1255
1242
|
fallback !== null
|
|
1256
1243
|
? [
|
|
@@ -1263,7 +1250,17 @@ function switch_statement_to_jsx_child(node, transform_context) {
|
|
|
1263
1250
|
]
|
|
1264
1251
|
: [];
|
|
1265
1252
|
|
|
1266
|
-
|
|
1253
|
+
const switch_element = create_jsx_element('Switch', attributes, match_children);
|
|
1254
|
+
|
|
1255
|
+
if (setup_statements.length === 0) {
|
|
1256
|
+
return switch_element;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
// Local-scoped helpers (typeOnly mode): wrap the <Switch> in an IIFE that
|
|
1260
|
+
// declares the helpers in source order and returns the element.
|
|
1261
|
+
return to_jsx_expression_container(
|
|
1262
|
+
b.call(b.arrow([], b.block([...setup_statements, b.return(switch_element)]))),
|
|
1263
|
+
);
|
|
1267
1264
|
}
|
|
1268
1265
|
|
|
1269
1266
|
/**
|
|
@@ -1336,28 +1333,13 @@ function try_statement_to_jsx_child(node, transform_context) {
|
|
|
1336
1333
|
const catch_jsx = body_to_jsx_child(catch_body_nodes, transform_context);
|
|
1337
1334
|
|
|
1338
1335
|
const fallback_fn = merge_branch_body_into_arrow(
|
|
1339
|
-
|
|
1340
|
-
type: 'ArrowFunctionExpression',
|
|
1341
|
-
params: catch_params,
|
|
1342
|
-
body: null,
|
|
1343
|
-
async: false,
|
|
1344
|
-
generator: false,
|
|
1345
|
-
expression: true,
|
|
1346
|
-
metadata: { path: [] },
|
|
1347
|
-
}),
|
|
1336
|
+
b.arrow(catch_params, b.literal(null)),
|
|
1348
1337
|
catch_jsx,
|
|
1349
1338
|
);
|
|
1350
1339
|
|
|
1351
1340
|
result = create_jsx_element(
|
|
1352
1341
|
'Errored',
|
|
1353
|
-
[
|
|
1354
|
-
{
|
|
1355
|
-
type: 'JSXAttribute',
|
|
1356
|
-
name: { type: 'JSXIdentifier', name: 'fallback', metadata: { path: [] } },
|
|
1357
|
-
value: to_jsx_expression_container(fallback_fn),
|
|
1358
|
-
metadata: { path: [] },
|
|
1359
|
-
},
|
|
1360
|
-
],
|
|
1342
|
+
[b.jsx_attribute(b.jsx_id('fallback'), to_jsx_expression_container(fallback_fn))],
|
|
1361
1343
|
[result],
|
|
1362
1344
|
);
|
|
1363
1345
|
}
|
|
@@ -1424,47 +1406,16 @@ function inject_solid_imports(program, transform_context) {
|
|
|
1424
1406
|
const specifiers = [];
|
|
1425
1407
|
|
|
1426
1408
|
if (transform_context.needs_ref_prop) {
|
|
1427
|
-
specifiers.push(
|
|
1428
|
-
type: 'ImportSpecifier',
|
|
1429
|
-
imported: { type: 'Identifier', name: 'create_ref_prop', metadata: { path: [] } },
|
|
1430
|
-
local: {
|
|
1431
|
-
type: 'Identifier',
|
|
1432
|
-
name: CREATE_REF_PROP_INTERNAL_NAME,
|
|
1433
|
-
metadata: { path: [] },
|
|
1434
|
-
},
|
|
1435
|
-
metadata: { path: [] },
|
|
1436
|
-
});
|
|
1409
|
+
specifiers.push(b.import_specifier('create_ref_prop', CREATE_REF_PROP_INTERNAL_NAME));
|
|
1437
1410
|
}
|
|
1438
1411
|
|
|
1439
1412
|
if (transform_context.needs_normalize_spread_props) {
|
|
1440
|
-
specifiers.push(
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
type: 'Identifier',
|
|
1444
|
-
name: 'normalize_spread_props',
|
|
1445
|
-
metadata: { path: [] },
|
|
1446
|
-
},
|
|
1447
|
-
local: {
|
|
1448
|
-
type: 'Identifier',
|
|
1449
|
-
name: NORMALIZE_SPREAD_PROPS_INTERNAL_NAME,
|
|
1450
|
-
metadata: { path: [] },
|
|
1451
|
-
},
|
|
1452
|
-
metadata: { path: [] },
|
|
1453
|
-
});
|
|
1413
|
+
specifiers.push(
|
|
1414
|
+
b.import_specifier('normalize_spread_props', NORMALIZE_SPREAD_PROPS_INTERNAL_NAME),
|
|
1415
|
+
);
|
|
1454
1416
|
}
|
|
1455
1417
|
|
|
1456
|
-
program.body.unshift(
|
|
1457
|
-
/** @type {any} */ ({
|
|
1458
|
-
type: 'ImportDeclaration',
|
|
1459
|
-
specifiers,
|
|
1460
|
-
source: {
|
|
1461
|
-
type: 'Literal',
|
|
1462
|
-
value: '@tsrx/solid/ref',
|
|
1463
|
-
raw: "'@tsrx/solid/ref'",
|
|
1464
|
-
},
|
|
1465
|
-
metadata: { path: [] },
|
|
1466
|
-
}),
|
|
1467
|
-
);
|
|
1418
|
+
program.body.unshift(b.import_declaration(specifiers, '@tsrx/solid/ref'));
|
|
1468
1419
|
}
|
|
1469
1420
|
|
|
1470
1421
|
const needed = [];
|
|
@@ -1477,20 +1428,11 @@ function inject_solid_imports(program, transform_context) {
|
|
|
1477
1428
|
|
|
1478
1429
|
if (needed.length === 0) return;
|
|
1479
1430
|
|
|
1480
|
-
const specifiers = needed.map((name) => ({
|
|
1481
|
-
type: 'ImportSpecifier',
|
|
1482
|
-
imported: { type: 'Identifier', name, metadata: { path: [] } },
|
|
1483
|
-
local: { type: 'Identifier', name, metadata: { path: [] } },
|
|
1484
|
-
metadata: { path: [] },
|
|
1485
|
-
}));
|
|
1486
|
-
|
|
1487
1431
|
program.body.unshift(
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
metadata: { path: [] },
|
|
1493
|
-
}),
|
|
1432
|
+
b.imports(
|
|
1433
|
+
needed.map((name) => [name, name]),
|
|
1434
|
+
'solid-js',
|
|
1435
|
+
),
|
|
1494
1436
|
);
|
|
1495
1437
|
}
|
|
1496
1438
|
|
|
@@ -1876,57 +1818,19 @@ function is_solid_jsx_ref_attribute(attr) {
|
|
|
1876
1818
|
*/
|
|
1877
1819
|
function dynamic_element_to_jsx_child(node, transform_context) {
|
|
1878
1820
|
const dynamic_id = set_loc(create_generated_identifier('DynamicElement'), node.id);
|
|
1879
|
-
const alias_declaration = set_loc(
|
|
1880
|
-
/** @type {any} */ ({
|
|
1881
|
-
type: 'VariableDeclaration',
|
|
1882
|
-
kind: 'const',
|
|
1883
|
-
declarations: [
|
|
1884
|
-
{
|
|
1885
|
-
type: 'VariableDeclarator',
|
|
1886
|
-
id: dynamic_id,
|
|
1887
|
-
init: clone_expression_node(node.id),
|
|
1888
|
-
metadata: { path: [] },
|
|
1889
|
-
},
|
|
1890
|
-
],
|
|
1891
|
-
metadata: { path: [] },
|
|
1892
|
-
}),
|
|
1893
|
-
node,
|
|
1894
|
-
);
|
|
1821
|
+
const alias_declaration = set_loc(b.const(dynamic_id, clone_expression_node(node.id)), node);
|
|
1895
1822
|
const jsx_element = create_dynamic_jsx_element(dynamic_id, node, transform_context);
|
|
1896
1823
|
|
|
1897
1824
|
return to_jsx_expression_container(
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
{
|
|
1908
|
-
type: 'ReturnStatement',
|
|
1909
|
-
argument: {
|
|
1910
|
-
type: 'ConditionalExpression',
|
|
1911
|
-
test: clone_identifier(dynamic_id),
|
|
1912
|
-
consequent: jsx_element,
|
|
1913
|
-
alternate: create_null_literal(),
|
|
1914
|
-
metadata: { path: [] },
|
|
1915
|
-
},
|
|
1916
|
-
metadata: { path: [] },
|
|
1917
|
-
},
|
|
1918
|
-
],
|
|
1919
|
-
metadata: { path: [] },
|
|
1920
|
-
}),
|
|
1921
|
-
async: false,
|
|
1922
|
-
generator: false,
|
|
1923
|
-
expression: false,
|
|
1924
|
-
metadata: { path: [] },
|
|
1925
|
-
},
|
|
1926
|
-
arguments: [],
|
|
1927
|
-
optional: false,
|
|
1928
|
-
metadata: { path: [] },
|
|
1929
|
-
}),
|
|
1825
|
+
b.call(
|
|
1826
|
+
b.arrow(
|
|
1827
|
+
[],
|
|
1828
|
+
b.block([
|
|
1829
|
+
alias_declaration,
|
|
1830
|
+
b.return(b.conditional(clone_identifier(dynamic_id), jsx_element, create_null_literal())),
|
|
1831
|
+
]),
|
|
1832
|
+
),
|
|
1833
|
+
),
|
|
1930
1834
|
node,
|
|
1931
1835
|
);
|
|
1932
1836
|
}
|