@tsrx/solid 0.0.7 → 0.0.9
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 +2 -2
- package/src/transform.js +136 -439
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.0.
|
|
6
|
+
"version": "0.0.9",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"publishConfig": {
|
|
9
9
|
"access": "public"
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"esrap": "^2.1.0",
|
|
24
24
|
"zimmerframe": "^1.1.2",
|
|
25
|
-
"@tsrx/core": "0.0.
|
|
25
|
+
"@tsrx/core": "0.0.9"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"solid-js": ">=1.8 || >=2.0.0-beta"
|
package/src/transform.js
CHANGED
|
@@ -1,22 +1,30 @@
|
|
|
1
1
|
/** @import * as AST from 'estree' */
|
|
2
2
|
/** @import * as ESTreeJSX from 'estree-jsx' */
|
|
3
3
|
|
|
4
|
-
import { walk } from 'zimmerframe';
|
|
5
|
-
import { print } from 'esrap';
|
|
6
|
-
import tsx from 'esrap/languages/tsx';
|
|
7
4
|
import {
|
|
8
|
-
|
|
5
|
+
createJsxTransform,
|
|
9
6
|
setLocation,
|
|
10
7
|
applyLazyTransforms as apply_lazy_transforms,
|
|
11
|
-
findFirstTopLevelAwaitInComponentBody as find_first_top_level_await_in_component_body,
|
|
12
8
|
collectLazyBindingsFromComponent as collect_lazy_bindings_from_component,
|
|
13
|
-
preallocateLazyIds as preallocate_lazy_ids,
|
|
14
9
|
replaceLazyParams as replace_lazy_params,
|
|
15
|
-
prepareStylesheetForRender as prepare_stylesheet_for_render,
|
|
16
|
-
annotateComponentWithHash as annotate_component_with_hash,
|
|
17
10
|
isInterleavedBody as is_interleaved_body_core,
|
|
18
11
|
isCapturableJsxChild as is_capturable_jsx_child,
|
|
19
12
|
captureJsxChild,
|
|
13
|
+
tsxNodeToJsxExpression as tsx_node_to_jsx_expression,
|
|
14
|
+
// Shared AST builders (truly platform-agnostic utilities).
|
|
15
|
+
clone_expression_node,
|
|
16
|
+
clone_identifier,
|
|
17
|
+
clone_jsx_name,
|
|
18
|
+
create_compile_error,
|
|
19
|
+
create_generated_identifier,
|
|
20
|
+
create_null_literal,
|
|
21
|
+
flatten_switch_consequent,
|
|
22
|
+
get_for_of_iteration_params,
|
|
23
|
+
identifier_to_jsx_name,
|
|
24
|
+
is_dynamic_element_id,
|
|
25
|
+
is_jsx_child,
|
|
26
|
+
set_loc,
|
|
27
|
+
to_text_expression,
|
|
20
28
|
} from '@tsrx/core';
|
|
21
29
|
|
|
22
30
|
/**
|
|
@@ -37,149 +45,75 @@ import {
|
|
|
37
45
|
*/
|
|
38
46
|
|
|
39
47
|
/**
|
|
40
|
-
*
|
|
48
|
+
* Solid platform descriptor consumed by `createJsxTransform`. Everything
|
|
49
|
+
* that diverges from React/Preact is plugged in via `hooks`:
|
|
50
|
+
* - Component-level `await` is rejected outright (no `"use server"` escape).
|
|
51
|
+
* - Control-flow statements become Solid's `<Show>` / `<For>` /
|
|
52
|
+
* `<Switch>/<Match>` / `<Errored>/<Loading>` instead of inline JSX.
|
|
53
|
+
* - `component` declarations run once at setup, with early-return JSX
|
|
54
|
+
* hoisted into a reactive `<Show when={!cond}>`.
|
|
55
|
+
* - Element attributes support composite elements and lift a lone
|
|
56
|
+
* `{text ...}` child into a `textContent` attribute.
|
|
57
|
+
* - `needs_show` / `needs_for` / etc. flags track which runtime
|
|
58
|
+
* primitives must be imported, injected by `inject_solid_imports`.
|
|
41
59
|
*
|
|
42
|
-
*
|
|
43
|
-
* returns Solid JSX. Control flow statements are rewritten to Solid's
|
|
44
|
-
* built-in components (`<Show>`, `<Switch>/<Match>`, `<For>`, `<Errored>`,
|
|
45
|
-
* `<Loading>`) so they remain reactive. Per-component `<style>` blocks are
|
|
46
|
-
* collected, rendered via `@tsrx/core`'s stylesheet renderer, and returned
|
|
47
|
-
* alongside the JS output so a downstream plugin can inject them.
|
|
48
|
-
*
|
|
49
|
-
* @param {AST.Program} ast
|
|
50
|
-
* @param {string} source
|
|
51
|
-
* @param {string} [filename]
|
|
52
|
-
* @returns {{ ast: AST.Program, code: string, map: any, css: { code: string, hash: string } | null }}
|
|
60
|
+
* @type {import('@tsrx/core/types').JsxPlatform}
|
|
53
61
|
*/
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const css = as_any.css;
|
|
93
|
-
if (css) {
|
|
94
|
-
stylesheets.push(css);
|
|
95
|
-
annotate_component_with_hash(as_any, css.hash);
|
|
96
|
-
}
|
|
97
|
-
return next(state);
|
|
98
|
-
},
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
// Second pass: transform Components, Elements, Text nodes, Tsx blocks, etc.
|
|
102
|
-
const transformed = walk(/** @type {any} */ (ast), transform_context, {
|
|
103
|
-
Component(node, { next, state }) {
|
|
104
|
-
const as_any = /** @type {any} */ (node);
|
|
105
|
-
|
|
106
|
-
const saved_css_hash = state.current_css_hash;
|
|
107
|
-
state.current_css_hash = as_any.css ? as_any.css.hash : null;
|
|
108
|
-
|
|
109
|
-
const inner = /** @type {any} */ (next() ?? node);
|
|
110
|
-
|
|
111
|
-
state.current_css_hash = saved_css_hash;
|
|
112
|
-
|
|
113
|
-
return /** @type {any} */ (component_to_function_declaration(inner, state));
|
|
114
|
-
},
|
|
115
|
-
|
|
116
|
-
Tsx(node, { next }) {
|
|
117
|
-
const inner = /** @type {any} */ (next() ?? node);
|
|
118
|
-
return /** @type {any} */ (tsx_node_to_jsx_expression(inner));
|
|
119
|
-
},
|
|
120
|
-
|
|
121
|
-
TsxCompat(node, { next }) {
|
|
122
|
-
const inner = /** @type {any} */ (next() ?? node);
|
|
123
|
-
return /** @type {any} */ (tsx_compat_node_to_jsx_expression(inner));
|
|
124
|
-
},
|
|
125
|
-
|
|
126
|
-
Element(node, { next, state }) {
|
|
127
|
-
const inner = /** @type {any} */ (next() ?? node);
|
|
128
|
-
return /** @type {any} */ (to_jsx_element(inner, state));
|
|
129
|
-
},
|
|
130
|
-
|
|
131
|
-
// `Text` nodes are lowered by `to_jsx_child` (and the `textContent`
|
|
132
|
-
// optimization in `to_jsx_element`) rather than the walker, so the
|
|
133
|
-
// parent element still sees a raw `Text` child when it runs and can
|
|
134
|
-
// decide whether to hoist it up to an attribute.
|
|
135
|
-
TSRXExpression(node, { next }) {
|
|
136
|
-
const inner = /** @type {any} */ (next() ?? node);
|
|
137
|
-
return /** @type {any} */ (to_jsx_expression_container(inner.expression, inner));
|
|
62
|
+
const solid_platform = {
|
|
63
|
+
name: 'Solid',
|
|
64
|
+
imports: {
|
|
65
|
+
// Solid doesn't use the React-style Suspense / ErrorBoundary pair.
|
|
66
|
+
// Both fields are here to satisfy the descriptor shape; actual
|
|
67
|
+
// import injection goes through `hooks.injectImports`.
|
|
68
|
+
suspense: 'solid-js',
|
|
69
|
+
errorBoundary: 'solid-js',
|
|
70
|
+
},
|
|
71
|
+
jsx: {
|
|
72
|
+
rewriteClassAttr: false,
|
|
73
|
+
acceptedTsxKinds: ['solid'],
|
|
74
|
+
},
|
|
75
|
+
validation: {
|
|
76
|
+
requireUseServerForAwait: true,
|
|
77
|
+
// Solid's custom validator always rejects component-level await,
|
|
78
|
+
// so directive scanning is redundant work. Keep the fallback flag
|
|
79
|
+
// above true as a safety net if the custom hook is removed.
|
|
80
|
+
scanUseServerDirectiveForAwaitWithCustomValidator: false,
|
|
81
|
+
},
|
|
82
|
+
hooks: {
|
|
83
|
+
initialState: () => ({
|
|
84
|
+
needs_show: false,
|
|
85
|
+
needs_for: false,
|
|
86
|
+
needs_switch: false,
|
|
87
|
+
needs_match: false,
|
|
88
|
+
needs_errored: false,
|
|
89
|
+
needs_loading: false,
|
|
90
|
+
}),
|
|
91
|
+
validateComponentAwait: (await_expression, _component, _ctx, _requires, source) => {
|
|
92
|
+
const await_start = get_await_keyword_start(await_expression, source);
|
|
93
|
+
const adjusted_node = /** @type {any} */ ({
|
|
94
|
+
...await_expression,
|
|
95
|
+
start: await_start,
|
|
96
|
+
end: await_start + 'await'.length,
|
|
97
|
+
});
|
|
98
|
+
throw create_compile_error(adjusted_node, '`await` is not allowed inside Solid components.');
|
|
138
99
|
},
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const value = `${state.current_css_hash} ${class_name}`;
|
|
145
|
-
return /** @type {any} */ ({ type: 'Literal', value, raw: JSON.stringify(value) });
|
|
146
|
-
}
|
|
147
|
-
return next();
|
|
100
|
+
controlFlow: {
|
|
101
|
+
ifStatement: if_statement_to_jsx_child,
|
|
102
|
+
forOf: for_of_statement_to_jsx_child,
|
|
103
|
+
switchStatement: switch_statement_to_jsx_child,
|
|
104
|
+
tryStatement: try_statement_to_jsx_child,
|
|
148
105
|
},
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const result = print(/** @type {any} */ (final_program), tsx(), {
|
|
162
|
-
sourceMapSource: filename,
|
|
163
|
-
sourceMapContent: source,
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
const css =
|
|
167
|
-
stylesheets.length > 0
|
|
168
|
-
? {
|
|
169
|
-
code: renderStylesheets(
|
|
170
|
-
/** @type {any} */ (stylesheets.map(prepare_stylesheet_for_render)),
|
|
171
|
-
),
|
|
172
|
-
hash: stylesheets.map((s) => s.hash).join(' '),
|
|
173
|
-
}
|
|
174
|
-
: null;
|
|
175
|
-
|
|
176
|
-
return {
|
|
177
|
-
ast: /** @type {AST.Program} */ (final_program),
|
|
178
|
-
code: result.code,
|
|
179
|
-
map: result.map,
|
|
180
|
-
css,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
106
|
+
componentToFunction: (component, ctx) =>
|
|
107
|
+
component_to_function_declaration(component, /** @type {any} */ (ctx)),
|
|
108
|
+
injectImports: (program, ctx) => inject_solid_imports(program, /** @type {any} */ (ctx)),
|
|
109
|
+
transformElementAttributes: (attrs, ctx, element) =>
|
|
110
|
+
transform_element_attributes(attrs, is_composite_element(element), /** @type {any} */ (ctx)),
|
|
111
|
+
transformElement: (inner, ctx, raw_children) =>
|
|
112
|
+
to_jsx_element(/** @type {any} */ (inner), /** @type {any} */ (ctx), raw_children),
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const transform = createJsxTransform(solid_platform);
|
|
183
117
|
|
|
184
118
|
/**
|
|
185
119
|
* @param {any} await_node
|
|
@@ -373,31 +307,6 @@ function component_to_function_declaration(component, transform_context) {
|
|
|
373
307
|
// Control flow → Solid JSX components
|
|
374
308
|
// =====================================================================
|
|
375
309
|
|
|
376
|
-
/**
|
|
377
|
-
* @param {any} node
|
|
378
|
-
* @returns {boolean}
|
|
379
|
-
*/
|
|
380
|
-
function is_jsx_child(node) {
|
|
381
|
-
if (!node) return false;
|
|
382
|
-
const t = node.type;
|
|
383
|
-
return (
|
|
384
|
-
t === 'JSXElement' ||
|
|
385
|
-
t === 'JSXFragment' ||
|
|
386
|
-
t === 'JSXExpressionContainer' ||
|
|
387
|
-
t === 'JSXText' ||
|
|
388
|
-
t === 'Tsx' ||
|
|
389
|
-
t === 'TsxCompat' ||
|
|
390
|
-
t === 'Element' ||
|
|
391
|
-
t === 'Text' ||
|
|
392
|
-
t === 'TSRXExpression' ||
|
|
393
|
-
t === 'Html' ||
|
|
394
|
-
t === 'IfStatement' ||
|
|
395
|
-
t === 'ForOfStatement' ||
|
|
396
|
-
t === 'SwitchStatement' ||
|
|
397
|
-
t === 'TryStatement'
|
|
398
|
-
);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
310
|
/**
|
|
402
311
|
* @param {any} node
|
|
403
312
|
* @param {TransformContext} transform_context
|
|
@@ -407,9 +316,11 @@ function to_jsx_child(node, transform_context) {
|
|
|
407
316
|
if (!node) return node;
|
|
408
317
|
switch (node.type) {
|
|
409
318
|
case 'Tsx':
|
|
410
|
-
|
|
319
|
+
// We're inside a JSX child position by construction; keep `{expr}`
|
|
320
|
+
// containers wrapped. See helpers.js.
|
|
321
|
+
return tsx_node_to_jsx_expression(node, true);
|
|
411
322
|
case 'TsxCompat':
|
|
412
|
-
return tsx_compat_node_to_jsx_expression(node);
|
|
323
|
+
return tsx_compat_node_to_jsx_expression(node, true);
|
|
413
324
|
case 'Element':
|
|
414
325
|
return to_jsx_element(node, transform_context);
|
|
415
326
|
case 'Text':
|
|
@@ -1059,6 +970,9 @@ function create_jsx_element(tag_name, attributes, children) {
|
|
|
1059
970
|
};
|
|
1060
971
|
}
|
|
1061
972
|
|
|
973
|
+
const TEMPLATE_FRAGMENT_ERROR =
|
|
974
|
+
'JSX fragment syntax is not needed in TSRX templates. TSRX renders in immediate mode, so everything is already a fragment. Use `<>...</>` only within <tsx>...</tsx>.';
|
|
975
|
+
|
|
1062
976
|
/**
|
|
1063
977
|
* Inject `import { Show, For, Switch, Match, Errored, Loading } from 'solid-js'`
|
|
1064
978
|
* specifiers for whichever control-flow primitives the transform emitted.
|
|
@@ -1099,11 +1013,22 @@ function inject_solid_imports(program, transform_context) {
|
|
|
1099
1013
|
// =====================================================================
|
|
1100
1014
|
|
|
1101
1015
|
/**
|
|
1102
|
-
* @param {any} node
|
|
1016
|
+
* @param {any} node - walker-transformed Element whose `children` have
|
|
1017
|
+
* already had `StyleIdentifier` / `TSRXExpression` / nested `Element`
|
|
1018
|
+
* walker rewrites applied.
|
|
1103
1019
|
* @param {TransformContext} transform_context
|
|
1020
|
+
* @param {any[]} [pre_walk_children] - optional pre-walk children list
|
|
1021
|
+
* from the `transformElement` hook. Only used to detect the
|
|
1022
|
+
* "single `Text` child" shape for the `textContent` optimization —
|
|
1023
|
+
* once detected we build the attribute from the original `Text.expression`.
|
|
1024
|
+
* The factory's `Text` walker lowers `Text` → `JSXExpressionContainer`, so
|
|
1025
|
+
* without these we'd miss the optimization. For rendering non-textContent
|
|
1026
|
+
* children we keep using `node.children` (walker-transformed), so
|
|
1027
|
+
* `MemberExpression` rewrites on `StyleIdentifier` refs inside children
|
|
1028
|
+
* are preserved.
|
|
1104
1029
|
* @returns {any}
|
|
1105
1030
|
*/
|
|
1106
|
-
function to_jsx_element(node, transform_context) {
|
|
1031
|
+
function to_jsx_element(node, transform_context, pre_walk_children) {
|
|
1107
1032
|
if (node.type === 'JSXElement') return node;
|
|
1108
1033
|
|
|
1109
1034
|
// `{html expr}` isn't supported on the Solid target — users should reach
|
|
@@ -1112,13 +1037,18 @@ function to_jsx_element(node, transform_context) {
|
|
|
1112
1037
|
// explicit in their source. Only Ripple has a `{html ...}` primitive.
|
|
1113
1038
|
// The check runs before the dynamic-element branch so `<@Dyn>{html x}</@Dyn>`
|
|
1114
1039
|
// fails with the same diagnostic as the static-element case.
|
|
1115
|
-
const
|
|
1116
|
-
|
|
1040
|
+
const walked_children = node.children || [];
|
|
1041
|
+
const text_optimization_children = pre_walk_children ?? walked_children;
|
|
1042
|
+
if (walked_children.some((/** @type {any} */ c) => c && c.type === 'Html')) {
|
|
1117
1043
|
throw new Error(
|
|
1118
1044
|
'`{html ...}` is not supported on the Solid target. Use `innerHTML={...}` as an element attribute instead.',
|
|
1119
1045
|
);
|
|
1120
1046
|
}
|
|
1121
1047
|
|
|
1048
|
+
if (!node.id) {
|
|
1049
|
+
throw create_compile_error(node, TEMPLATE_FRAGMENT_ERROR);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1122
1052
|
if (is_dynamic_element_id(node.id)) {
|
|
1123
1053
|
return dynamic_element_to_jsx_child(node, transform_context);
|
|
1124
1054
|
}
|
|
@@ -1139,16 +1069,20 @@ function to_jsx_element(node, transform_context) {
|
|
|
1139
1069
|
// the parent is a host element (composite components receive
|
|
1140
1070
|
// `textContent` as an opaque prop with no DOM semantics), and when the
|
|
1141
1071
|
// user hasn't already set `textContent` themselves.
|
|
1072
|
+
//
|
|
1073
|
+
// We check `text_optimization_children` (pre-walk) rather than
|
|
1074
|
+
// `walked_children` because the factory's `Text` walker has already
|
|
1075
|
+
// lowered `Text` → `JSXExpressionContainer`, which wouldn't match.
|
|
1142
1076
|
let selfClosing = !!node.selfClosing;
|
|
1143
1077
|
let children;
|
|
1144
1078
|
if (
|
|
1145
1079
|
!is_composite &&
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1080
|
+
text_optimization_children.length === 1 &&
|
|
1081
|
+
text_optimization_children[0] &&
|
|
1082
|
+
text_optimization_children[0].type === 'Text' &&
|
|
1149
1083
|
!has_text_content_attribute(attributes)
|
|
1150
1084
|
) {
|
|
1151
|
-
const text_child =
|
|
1085
|
+
const text_child = text_optimization_children[0];
|
|
1152
1086
|
attributes.push(
|
|
1153
1087
|
set_loc(
|
|
1154
1088
|
/** @type {any} */ ({
|
|
@@ -1158,10 +1092,14 @@ function to_jsx_element(node, transform_context) {
|
|
|
1158
1092
|
name: 'textContent',
|
|
1159
1093
|
metadata: { path: [] },
|
|
1160
1094
|
},
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1095
|
+
// preserves the walker's rewrites on the Text's inner expression
|
|
1096
|
+
value:
|
|
1097
|
+
walked_children[0] && walked_children[0].type === 'JSXExpressionContainer'
|
|
1098
|
+
? walked_children[0]
|
|
1099
|
+
: to_jsx_expression_container(
|
|
1100
|
+
to_text_expression(text_child.expression, text_child),
|
|
1101
|
+
text_child,
|
|
1102
|
+
),
|
|
1165
1103
|
shorthand: false,
|
|
1166
1104
|
metadata: { path: [] },
|
|
1167
1105
|
}),
|
|
@@ -1171,7 +1109,10 @@ function to_jsx_element(node, transform_context) {
|
|
|
1171
1109
|
children = [];
|
|
1172
1110
|
selfClosing = true;
|
|
1173
1111
|
} else {
|
|
1174
|
-
children
|
|
1112
|
+
// Use walker-transformed children so `MemberExpression` /
|
|
1113
|
+
// `StyleIdentifier` rewrites from the factory walker are preserved
|
|
1114
|
+
// in the emitted JSX.
|
|
1115
|
+
children = create_element_children(walked_children, transform_context);
|
|
1175
1116
|
}
|
|
1176
1117
|
|
|
1177
1118
|
const openingElement = set_loc(
|
|
@@ -1189,7 +1130,11 @@ function to_jsx_element(node, transform_context) {
|
|
|
1189
1130
|
: set_loc(
|
|
1190
1131
|
/** @type {any} */ ({
|
|
1191
1132
|
type: 'JSXClosingElement',
|
|
1192
|
-
name
|
|
1133
|
+
// Forward the source *name* (not the JSXClosingElement wrapper)
|
|
1134
|
+
// so `clone_jsx_name` can propagate member-expression sub-part
|
|
1135
|
+
// locations from the closing tag. See the identical fix in
|
|
1136
|
+
// packages/tsrx/src/transform/jsx/index.js.
|
|
1137
|
+
name: clone_jsx_name(name, node.closingElement?.name || node.closingElement || node),
|
|
1193
1138
|
}),
|
|
1194
1139
|
node.closingElement || node,
|
|
1195
1140
|
);
|
|
@@ -1269,17 +1214,6 @@ function to_jsx_attribute(attr) {
|
|
|
1269
1214
|
);
|
|
1270
1215
|
}
|
|
1271
1216
|
|
|
1272
|
-
/**
|
|
1273
|
-
* @param {any} id
|
|
1274
|
-
* @returns {boolean}
|
|
1275
|
-
*/
|
|
1276
|
-
function is_dynamic_element_id(id) {
|
|
1277
|
-
if (!id || typeof id !== 'object') return false;
|
|
1278
|
-
if (id.type === 'Identifier') return !!id.tracked;
|
|
1279
|
-
if (id.type === 'MemberExpression') return is_dynamic_element_id(id.object);
|
|
1280
|
-
return false;
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
1217
|
/**
|
|
1284
1218
|
* Detect whether an `Element` node represents a composite component (tag
|
|
1285
1219
|
* name starts with an uppercase letter, or is a member expression like
|
|
@@ -1531,39 +1465,6 @@ function to_jsx_expression_container(expression, source_node = expression) {
|
|
|
1531
1465
|
);
|
|
1532
1466
|
}
|
|
1533
1467
|
|
|
1534
|
-
/**
|
|
1535
|
-
* `{text expr}` → `expr == null ? '' : expr + ''` — coerce to string,
|
|
1536
|
-
* matching React's text semantics so booleans/objects render as text.
|
|
1537
|
-
*
|
|
1538
|
-
* @param {AST.Expression} expression
|
|
1539
|
-
* @param {any} [source_node]
|
|
1540
|
-
* @returns {AST.Expression}
|
|
1541
|
-
*/
|
|
1542
|
-
function to_text_expression(expression, source_node = expression) {
|
|
1543
|
-
return set_loc(
|
|
1544
|
-
/** @type {AST.Expression} */ ({
|
|
1545
|
-
type: 'ConditionalExpression',
|
|
1546
|
-
test: {
|
|
1547
|
-
type: 'BinaryExpression',
|
|
1548
|
-
operator: '==',
|
|
1549
|
-
left: clone_expression_node(expression),
|
|
1550
|
-
right: { type: 'Literal', value: null, raw: 'null', metadata: { path: [] } },
|
|
1551
|
-
metadata: { path: [] },
|
|
1552
|
-
},
|
|
1553
|
-
consequent: { type: 'Literal', value: '', raw: "''", metadata: { path: [] } },
|
|
1554
|
-
alternate: {
|
|
1555
|
-
type: 'BinaryExpression',
|
|
1556
|
-
operator: '+',
|
|
1557
|
-
left: clone_expression_node(expression),
|
|
1558
|
-
right: { type: 'Literal', value: '', raw: "''", metadata: { path: [] } },
|
|
1559
|
-
metadata: { path: [] },
|
|
1560
|
-
},
|
|
1561
|
-
metadata: { path: [] },
|
|
1562
|
-
}),
|
|
1563
|
-
source_node,
|
|
1564
|
-
);
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
1468
|
/**
|
|
1568
1469
|
* @param {any[]} render_nodes
|
|
1569
1470
|
* @returns {any}
|
|
@@ -1591,221 +1492,17 @@ function build_return_expression(render_nodes) {
|
|
|
1591
1492
|
);
|
|
1592
1493
|
}
|
|
1593
1494
|
|
|
1594
|
-
/**
|
|
1595
|
-
* @param {AST.Identifier | AST.MemberExpression | any} id
|
|
1596
|
-
* @returns {any}
|
|
1597
|
-
*/
|
|
1598
|
-
function identifier_to_jsx_name(id) {
|
|
1599
|
-
if (id.type === 'Identifier') {
|
|
1600
|
-
return set_loc(
|
|
1601
|
-
/** @type {any} */ ({
|
|
1602
|
-
type: 'JSXIdentifier',
|
|
1603
|
-
name: id.name,
|
|
1604
|
-
metadata: { path: [], is_component: /^[A-Z]/.test(id.name) },
|
|
1605
|
-
}),
|
|
1606
|
-
id,
|
|
1607
|
-
);
|
|
1608
|
-
}
|
|
1609
|
-
if (id.type === 'MemberExpression') {
|
|
1610
|
-
return set_loc(
|
|
1611
|
-
/** @type {any} */ ({
|
|
1612
|
-
type: 'JSXMemberExpression',
|
|
1613
|
-
object: /** @type {any} */ (identifier_to_jsx_name(id.object)),
|
|
1614
|
-
property: /** @type {any} */ (identifier_to_jsx_name(id.property)),
|
|
1615
|
-
}),
|
|
1616
|
-
id,
|
|
1617
|
-
);
|
|
1618
|
-
}
|
|
1619
|
-
return id;
|
|
1620
|
-
}
|
|
1621
|
-
|
|
1622
|
-
/**
|
|
1623
|
-
* @param {any} name
|
|
1624
|
-
* @param {any} [source_node]
|
|
1625
|
-
* @returns {any}
|
|
1626
|
-
*/
|
|
1627
|
-
function clone_jsx_name(name, source_node = name) {
|
|
1628
|
-
if (name.type === 'JSXIdentifier') {
|
|
1629
|
-
return set_loc(
|
|
1630
|
-
{ type: 'JSXIdentifier', name: name.name, metadata: name.metadata || { path: [] } },
|
|
1631
|
-
source_node,
|
|
1632
|
-
);
|
|
1633
|
-
}
|
|
1634
|
-
if (name.type === 'JSXMemberExpression') {
|
|
1635
|
-
return set_loc(
|
|
1636
|
-
{
|
|
1637
|
-
type: 'JSXMemberExpression',
|
|
1638
|
-
object: clone_jsx_name(name.object, source_node.object || name.object),
|
|
1639
|
-
property: clone_jsx_name(name.property, source_node.property || name.property),
|
|
1640
|
-
metadata: name.metadata || { path: [] },
|
|
1641
|
-
},
|
|
1642
|
-
source_node,
|
|
1643
|
-
);
|
|
1644
|
-
}
|
|
1645
|
-
return name;
|
|
1646
|
-
}
|
|
1647
|
-
|
|
1648
|
-
/**
|
|
1649
|
-
* @param {AST.Identifier} identifier
|
|
1650
|
-
* @returns {any}
|
|
1651
|
-
*/
|
|
1652
|
-
function clone_identifier(identifier) {
|
|
1653
|
-
return set_loc(
|
|
1654
|
-
/** @type {any} */ ({
|
|
1655
|
-
type: 'Identifier',
|
|
1656
|
-
name: identifier.name,
|
|
1657
|
-
metadata: { path: [] },
|
|
1658
|
-
}),
|
|
1659
|
-
identifier,
|
|
1660
|
-
);
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
/**
|
|
1664
|
-
* @param {any} node
|
|
1665
|
-
* @returns {any}
|
|
1666
|
-
*/
|
|
1667
|
-
function clone_expression_node(node) {
|
|
1668
|
-
if (!node || typeof node !== 'object') return node;
|
|
1669
|
-
if (Array.isArray(node)) return node.map(clone_expression_node);
|
|
1670
|
-
const clone = { ...node };
|
|
1671
|
-
for (const key of Object.keys(clone)) {
|
|
1672
|
-
// Positional keys are value-shared across nodes and — more importantly —
|
|
1673
|
-
// `loc` objects often contain back-references to sub-objects that would
|
|
1674
|
-
// blow the stack without a cycle guard. Every other AST traversal in
|
|
1675
|
-
// this file skips these; keep them as shallow-copied references.
|
|
1676
|
-
if (key === 'loc' || key === 'start' || key === 'end') continue;
|
|
1677
|
-
if (key === 'metadata') {
|
|
1678
|
-
clone.metadata = clone.metadata ? { ...clone.metadata } : { path: [] };
|
|
1679
|
-
continue;
|
|
1680
|
-
}
|
|
1681
|
-
clone[key] = clone_expression_node(clone[key]);
|
|
1682
|
-
}
|
|
1683
|
-
return clone;
|
|
1684
|
-
}
|
|
1685
|
-
|
|
1686
|
-
/**
|
|
1687
|
-
* @returns {AST.Literal}
|
|
1688
|
-
*/
|
|
1689
|
-
function create_null_literal() {
|
|
1690
|
-
return /** @type {any} */ ({ type: 'Literal', value: null, raw: 'null', metadata: { path: [] } });
|
|
1691
|
-
}
|
|
1692
|
-
|
|
1693
|
-
/**
|
|
1694
|
-
* @template T
|
|
1695
|
-
* @param {T} node
|
|
1696
|
-
* @param {any} source_node
|
|
1697
|
-
* @returns {T}
|
|
1698
|
-
*/
|
|
1699
|
-
function set_loc(node, source_node) {
|
|
1700
|
-
/** @type {any} */ (node).metadata ??= { path: [] };
|
|
1701
|
-
if (source_node?.loc) {
|
|
1702
|
-
return /** @type {T} */ (setLocation(/** @type {any} */ (node), source_node, true));
|
|
1703
|
-
}
|
|
1704
|
-
return node;
|
|
1705
|
-
}
|
|
1706
|
-
|
|
1707
|
-
/**
|
|
1708
|
-
* @param {any} left
|
|
1709
|
-
* @param {any} index
|
|
1710
|
-
* @returns {any[]}
|
|
1711
|
-
*/
|
|
1712
|
-
function get_for_of_iteration_params(left, index) {
|
|
1713
|
-
const params = [];
|
|
1714
|
-
if (left?.type === 'VariableDeclaration') {
|
|
1715
|
-
params.push(left.declarations[0]?.id);
|
|
1716
|
-
} else {
|
|
1717
|
-
params.push(left);
|
|
1718
|
-
}
|
|
1719
|
-
if (index) params.push(index);
|
|
1720
|
-
return params;
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1723
|
-
/**
|
|
1724
|
-
* @param {string} name
|
|
1725
|
-
* @returns {any}
|
|
1726
|
-
*/
|
|
1727
|
-
function create_generated_identifier(name) {
|
|
1728
|
-
return /** @type {any} */ ({ type: 'Identifier', name, metadata: { path: [] } });
|
|
1729
|
-
}
|
|
1730
|
-
|
|
1731
|
-
/**
|
|
1732
|
-
* @param {any} node
|
|
1733
|
-
* @param {string} message
|
|
1734
|
-
* @returns {Error & { pos: number, end: number }}
|
|
1735
|
-
*/
|
|
1736
|
-
function create_compile_error(node, message) {
|
|
1737
|
-
const error = /** @type {Error & { pos: number, end: number }} */ (new Error(message));
|
|
1738
|
-
error.pos = node.start ?? 0;
|
|
1739
|
-
error.end = node.end ?? error.pos + 1;
|
|
1740
|
-
return error;
|
|
1741
|
-
}
|
|
1742
|
-
|
|
1743
|
-
/**
|
|
1744
|
-
* @param {any[]} consequent
|
|
1745
|
-
* @returns {any[]}
|
|
1746
|
-
*/
|
|
1747
|
-
function flatten_switch_consequent(consequent) {
|
|
1748
|
-
const result = [];
|
|
1749
|
-
for (const node of consequent) {
|
|
1750
|
-
if (node.type === 'BlockStatement') result.push(...node.body);
|
|
1751
|
-
else result.push(node);
|
|
1752
|
-
}
|
|
1753
|
-
return result;
|
|
1754
|
-
}
|
|
1755
|
-
|
|
1756
1495
|
/**
|
|
1757
1496
|
* @param {any} node
|
|
1497
|
+
* @param {boolean} [in_jsx_child]
|
|
1758
1498
|
* @returns {any}
|
|
1759
1499
|
*/
|
|
1760
|
-
function tsx_compat_node_to_jsx_expression(node) {
|
|
1500
|
+
function tsx_compat_node_to_jsx_expression(node, in_jsx_child = false) {
|
|
1761
1501
|
if (node.kind !== 'solid') {
|
|
1762
1502
|
throw create_compile_error(
|
|
1763
1503
|
node,
|
|
1764
1504
|
`Solid TSRX does not support <tsx:${node.kind}> blocks. Use <tsx> or <tsx:solid>.`,
|
|
1765
1505
|
);
|
|
1766
1506
|
}
|
|
1767
|
-
return tsx_node_to_jsx_expression(node);
|
|
1768
|
-
}
|
|
1769
|
-
|
|
1770
|
-
/**
|
|
1771
|
-
* `<tsx>...</tsx>` → Solid JSX fragment (or single child if only one).
|
|
1772
|
-
*
|
|
1773
|
-
* @param {any} node
|
|
1774
|
-
* @returns {any}
|
|
1775
|
-
*/
|
|
1776
|
-
function tsx_node_to_jsx_expression(node) {
|
|
1777
|
-
const children = (node.children || []).filter(
|
|
1778
|
-
(/** @type {any} */ child) => child.type !== 'JSXText' || child.value.trim() !== '',
|
|
1779
|
-
);
|
|
1780
|
-
|
|
1781
|
-
if (children.length === 1 && children[0].type !== 'JSXText') {
|
|
1782
|
-
return strip_locations(children[0]);
|
|
1783
|
-
}
|
|
1784
|
-
|
|
1785
|
-
return strip_locations(
|
|
1786
|
-
/** @type {any} */ ({
|
|
1787
|
-
type: 'JSXFragment',
|
|
1788
|
-
openingFragment: { type: 'JSXOpeningFragment', metadata: { path: [] } },
|
|
1789
|
-
closingFragment: { type: 'JSXClosingFragment', metadata: { path: [] } },
|
|
1790
|
-
children,
|
|
1791
|
-
metadata: { path: [] },
|
|
1792
|
-
}),
|
|
1793
|
-
);
|
|
1794
|
-
}
|
|
1795
|
-
|
|
1796
|
-
/**
|
|
1797
|
-
* @param {any} node
|
|
1798
|
-
* @returns {any}
|
|
1799
|
-
*/
|
|
1800
|
-
function strip_locations(node) {
|
|
1801
|
-
if (!node || typeof node !== 'object') return node;
|
|
1802
|
-
if (Array.isArray(node)) return node.map(strip_locations);
|
|
1803
|
-
delete node.loc;
|
|
1804
|
-
delete node.start;
|
|
1805
|
-
delete node.end;
|
|
1806
|
-
for (const key of Object.keys(node)) {
|
|
1807
|
-
if (key === 'metadata') continue;
|
|
1808
|
-
node[key] = strip_locations(node[key]);
|
|
1809
|
-
}
|
|
1810
|
-
return node;
|
|
1507
|
+
return tsx_node_to_jsx_expression(node, in_jsx_child);
|
|
1811
1508
|
}
|