@tsrx/solid 0.0.8 → 0.0.10
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 +129 -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.10",
|
|
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.10"
|
|
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':
|
|
@@ -1102,11 +1013,22 @@ function inject_solid_imports(program, transform_context) {
|
|
|
1102
1013
|
// =====================================================================
|
|
1103
1014
|
|
|
1104
1015
|
/**
|
|
1105
|
-
* @param {any} node
|
|
1016
|
+
* @param {any} node - walker-transformed Element whose `children` have
|
|
1017
|
+
* already had `StyleIdentifier` / `TSRXExpression` / nested `Element`
|
|
1018
|
+
* walker rewrites applied.
|
|
1106
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.
|
|
1107
1029
|
* @returns {any}
|
|
1108
1030
|
*/
|
|
1109
|
-
function to_jsx_element(node, transform_context) {
|
|
1031
|
+
function to_jsx_element(node, transform_context, pre_walk_children) {
|
|
1110
1032
|
if (node.type === 'JSXElement') return node;
|
|
1111
1033
|
|
|
1112
1034
|
// `{html expr}` isn't supported on the Solid target — users should reach
|
|
@@ -1115,8 +1037,9 @@ function to_jsx_element(node, transform_context) {
|
|
|
1115
1037
|
// explicit in their source. Only Ripple has a `{html ...}` primitive.
|
|
1116
1038
|
// The check runs before the dynamic-element branch so `<@Dyn>{html x}</@Dyn>`
|
|
1117
1039
|
// fails with the same diagnostic as the static-element case.
|
|
1118
|
-
const
|
|
1119
|
-
|
|
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')) {
|
|
1120
1043
|
throw new Error(
|
|
1121
1044
|
'`{html ...}` is not supported on the Solid target. Use `innerHTML={...}` as an element attribute instead.',
|
|
1122
1045
|
);
|
|
@@ -1146,16 +1069,20 @@ function to_jsx_element(node, transform_context) {
|
|
|
1146
1069
|
// the parent is a host element (composite components receive
|
|
1147
1070
|
// `textContent` as an opaque prop with no DOM semantics), and when the
|
|
1148
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.
|
|
1149
1076
|
let selfClosing = !!node.selfClosing;
|
|
1150
1077
|
let children;
|
|
1151
1078
|
if (
|
|
1152
1079
|
!is_composite &&
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1080
|
+
text_optimization_children.length === 1 &&
|
|
1081
|
+
text_optimization_children[0] &&
|
|
1082
|
+
text_optimization_children[0].type === 'Text' &&
|
|
1156
1083
|
!has_text_content_attribute(attributes)
|
|
1157
1084
|
) {
|
|
1158
|
-
const text_child =
|
|
1085
|
+
const text_child = text_optimization_children[0];
|
|
1159
1086
|
attributes.push(
|
|
1160
1087
|
set_loc(
|
|
1161
1088
|
/** @type {any} */ ({
|
|
@@ -1165,10 +1092,14 @@ function to_jsx_element(node, transform_context) {
|
|
|
1165
1092
|
name: 'textContent',
|
|
1166
1093
|
metadata: { path: [] },
|
|
1167
1094
|
},
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
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
|
+
),
|
|
1172
1103
|
shorthand: false,
|
|
1173
1104
|
metadata: { path: [] },
|
|
1174
1105
|
}),
|
|
@@ -1178,7 +1109,10 @@ function to_jsx_element(node, transform_context) {
|
|
|
1178
1109
|
children = [];
|
|
1179
1110
|
selfClosing = true;
|
|
1180
1111
|
} else {
|
|
1181
|
-
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);
|
|
1182
1116
|
}
|
|
1183
1117
|
|
|
1184
1118
|
const openingElement = set_loc(
|
|
@@ -1196,7 +1130,11 @@ function to_jsx_element(node, transform_context) {
|
|
|
1196
1130
|
: set_loc(
|
|
1197
1131
|
/** @type {any} */ ({
|
|
1198
1132
|
type: 'JSXClosingElement',
|
|
1199
|
-
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),
|
|
1200
1138
|
}),
|
|
1201
1139
|
node.closingElement || node,
|
|
1202
1140
|
);
|
|
@@ -1276,17 +1214,6 @@ function to_jsx_attribute(attr) {
|
|
|
1276
1214
|
);
|
|
1277
1215
|
}
|
|
1278
1216
|
|
|
1279
|
-
/**
|
|
1280
|
-
* @param {any} id
|
|
1281
|
-
* @returns {boolean}
|
|
1282
|
-
*/
|
|
1283
|
-
function is_dynamic_element_id(id) {
|
|
1284
|
-
if (!id || typeof id !== 'object') return false;
|
|
1285
|
-
if (id.type === 'Identifier') return !!id.tracked;
|
|
1286
|
-
if (id.type === 'MemberExpression') return is_dynamic_element_id(id.object);
|
|
1287
|
-
return false;
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1290
1217
|
/**
|
|
1291
1218
|
* Detect whether an `Element` node represents a composite component (tag
|
|
1292
1219
|
* name starts with an uppercase letter, or is a member expression like
|
|
@@ -1538,39 +1465,6 @@ function to_jsx_expression_container(expression, source_node = expression) {
|
|
|
1538
1465
|
);
|
|
1539
1466
|
}
|
|
1540
1467
|
|
|
1541
|
-
/**
|
|
1542
|
-
* `{text expr}` → `expr == null ? '' : expr + ''` — coerce to string,
|
|
1543
|
-
* matching React's text semantics so booleans/objects render as text.
|
|
1544
|
-
*
|
|
1545
|
-
* @param {AST.Expression} expression
|
|
1546
|
-
* @param {any} [source_node]
|
|
1547
|
-
* @returns {AST.Expression}
|
|
1548
|
-
*/
|
|
1549
|
-
function to_text_expression(expression, source_node = expression) {
|
|
1550
|
-
return set_loc(
|
|
1551
|
-
/** @type {AST.Expression} */ ({
|
|
1552
|
-
type: 'ConditionalExpression',
|
|
1553
|
-
test: {
|
|
1554
|
-
type: 'BinaryExpression',
|
|
1555
|
-
operator: '==',
|
|
1556
|
-
left: clone_expression_node(expression),
|
|
1557
|
-
right: { type: 'Literal', value: null, raw: 'null', metadata: { path: [] } },
|
|
1558
|
-
metadata: { path: [] },
|
|
1559
|
-
},
|
|
1560
|
-
consequent: { type: 'Literal', value: '', raw: "''", metadata: { path: [] } },
|
|
1561
|
-
alternate: {
|
|
1562
|
-
type: 'BinaryExpression',
|
|
1563
|
-
operator: '+',
|
|
1564
|
-
left: clone_expression_node(expression),
|
|
1565
|
-
right: { type: 'Literal', value: '', raw: "''", metadata: { path: [] } },
|
|
1566
|
-
metadata: { path: [] },
|
|
1567
|
-
},
|
|
1568
|
-
metadata: { path: [] },
|
|
1569
|
-
}),
|
|
1570
|
-
source_node,
|
|
1571
|
-
);
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
1468
|
/**
|
|
1575
1469
|
* @param {any[]} render_nodes
|
|
1576
1470
|
* @returns {any}
|
|
@@ -1598,221 +1492,17 @@ function build_return_expression(render_nodes) {
|
|
|
1598
1492
|
);
|
|
1599
1493
|
}
|
|
1600
1494
|
|
|
1601
|
-
/**
|
|
1602
|
-
* @param {AST.Identifier | AST.MemberExpression | any} id
|
|
1603
|
-
* @returns {any}
|
|
1604
|
-
*/
|
|
1605
|
-
function identifier_to_jsx_name(id) {
|
|
1606
|
-
if (id.type === 'Identifier') {
|
|
1607
|
-
return set_loc(
|
|
1608
|
-
/** @type {any} */ ({
|
|
1609
|
-
type: 'JSXIdentifier',
|
|
1610
|
-
name: id.name,
|
|
1611
|
-
metadata: { path: [], is_component: /^[A-Z]/.test(id.name) },
|
|
1612
|
-
}),
|
|
1613
|
-
id,
|
|
1614
|
-
);
|
|
1615
|
-
}
|
|
1616
|
-
if (id.type === 'MemberExpression') {
|
|
1617
|
-
return set_loc(
|
|
1618
|
-
/** @type {any} */ ({
|
|
1619
|
-
type: 'JSXMemberExpression',
|
|
1620
|
-
object: /** @type {any} */ (identifier_to_jsx_name(id.object)),
|
|
1621
|
-
property: /** @type {any} */ (identifier_to_jsx_name(id.property)),
|
|
1622
|
-
}),
|
|
1623
|
-
id,
|
|
1624
|
-
);
|
|
1625
|
-
}
|
|
1626
|
-
return id;
|
|
1627
|
-
}
|
|
1628
|
-
|
|
1629
|
-
/**
|
|
1630
|
-
* @param {any} name
|
|
1631
|
-
* @param {any} [source_node]
|
|
1632
|
-
* @returns {any}
|
|
1633
|
-
*/
|
|
1634
|
-
function clone_jsx_name(name, source_node = name) {
|
|
1635
|
-
if (name.type === 'JSXIdentifier') {
|
|
1636
|
-
return set_loc(
|
|
1637
|
-
{ type: 'JSXIdentifier', name: name.name, metadata: name.metadata || { path: [] } },
|
|
1638
|
-
source_node,
|
|
1639
|
-
);
|
|
1640
|
-
}
|
|
1641
|
-
if (name.type === 'JSXMemberExpression') {
|
|
1642
|
-
return set_loc(
|
|
1643
|
-
{
|
|
1644
|
-
type: 'JSXMemberExpression',
|
|
1645
|
-
object: clone_jsx_name(name.object, source_node.object || name.object),
|
|
1646
|
-
property: clone_jsx_name(name.property, source_node.property || name.property),
|
|
1647
|
-
metadata: name.metadata || { path: [] },
|
|
1648
|
-
},
|
|
1649
|
-
source_node,
|
|
1650
|
-
);
|
|
1651
|
-
}
|
|
1652
|
-
return name;
|
|
1653
|
-
}
|
|
1654
|
-
|
|
1655
|
-
/**
|
|
1656
|
-
* @param {AST.Identifier} identifier
|
|
1657
|
-
* @returns {any}
|
|
1658
|
-
*/
|
|
1659
|
-
function clone_identifier(identifier) {
|
|
1660
|
-
return set_loc(
|
|
1661
|
-
/** @type {any} */ ({
|
|
1662
|
-
type: 'Identifier',
|
|
1663
|
-
name: identifier.name,
|
|
1664
|
-
metadata: { path: [] },
|
|
1665
|
-
}),
|
|
1666
|
-
identifier,
|
|
1667
|
-
);
|
|
1668
|
-
}
|
|
1669
|
-
|
|
1670
1495
|
/**
|
|
1671
1496
|
* @param {any} node
|
|
1497
|
+
* @param {boolean} [in_jsx_child]
|
|
1672
1498
|
* @returns {any}
|
|
1673
1499
|
*/
|
|
1674
|
-
function
|
|
1675
|
-
if (!node || typeof node !== 'object') return node;
|
|
1676
|
-
if (Array.isArray(node)) return node.map(clone_expression_node);
|
|
1677
|
-
const clone = { ...node };
|
|
1678
|
-
for (const key of Object.keys(clone)) {
|
|
1679
|
-
// Positional keys are value-shared across nodes and — more importantly —
|
|
1680
|
-
// `loc` objects often contain back-references to sub-objects that would
|
|
1681
|
-
// blow the stack without a cycle guard. Every other AST traversal in
|
|
1682
|
-
// this file skips these; keep them as shallow-copied references.
|
|
1683
|
-
if (key === 'loc' || key === 'start' || key === 'end') continue;
|
|
1684
|
-
if (key === 'metadata') {
|
|
1685
|
-
clone.metadata = clone.metadata ? { ...clone.metadata } : { path: [] };
|
|
1686
|
-
continue;
|
|
1687
|
-
}
|
|
1688
|
-
clone[key] = clone_expression_node(clone[key]);
|
|
1689
|
-
}
|
|
1690
|
-
return clone;
|
|
1691
|
-
}
|
|
1692
|
-
|
|
1693
|
-
/**
|
|
1694
|
-
* @returns {AST.Literal}
|
|
1695
|
-
*/
|
|
1696
|
-
function create_null_literal() {
|
|
1697
|
-
return /** @type {any} */ ({ type: 'Literal', value: null, raw: 'null', metadata: { path: [] } });
|
|
1698
|
-
}
|
|
1699
|
-
|
|
1700
|
-
/**
|
|
1701
|
-
* @template T
|
|
1702
|
-
* @param {T} node
|
|
1703
|
-
* @param {any} source_node
|
|
1704
|
-
* @returns {T}
|
|
1705
|
-
*/
|
|
1706
|
-
function set_loc(node, source_node) {
|
|
1707
|
-
/** @type {any} */ (node).metadata ??= { path: [] };
|
|
1708
|
-
if (source_node?.loc) {
|
|
1709
|
-
return /** @type {T} */ (setLocation(/** @type {any} */ (node), source_node, true));
|
|
1710
|
-
}
|
|
1711
|
-
return node;
|
|
1712
|
-
}
|
|
1713
|
-
|
|
1714
|
-
/**
|
|
1715
|
-
* @param {any} left
|
|
1716
|
-
* @param {any} index
|
|
1717
|
-
* @returns {any[]}
|
|
1718
|
-
*/
|
|
1719
|
-
function get_for_of_iteration_params(left, index) {
|
|
1720
|
-
const params = [];
|
|
1721
|
-
if (left?.type === 'VariableDeclaration') {
|
|
1722
|
-
params.push(left.declarations[0]?.id);
|
|
1723
|
-
} else {
|
|
1724
|
-
params.push(left);
|
|
1725
|
-
}
|
|
1726
|
-
if (index) params.push(index);
|
|
1727
|
-
return params;
|
|
1728
|
-
}
|
|
1729
|
-
|
|
1730
|
-
/**
|
|
1731
|
-
* @param {string} name
|
|
1732
|
-
* @returns {any}
|
|
1733
|
-
*/
|
|
1734
|
-
function create_generated_identifier(name) {
|
|
1735
|
-
return /** @type {any} */ ({ type: 'Identifier', name, metadata: { path: [] } });
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1738
|
-
/**
|
|
1739
|
-
* @param {any} node
|
|
1740
|
-
* @param {string} message
|
|
1741
|
-
* @returns {Error & { pos: number, end: number }}
|
|
1742
|
-
*/
|
|
1743
|
-
function create_compile_error(node, message) {
|
|
1744
|
-
const error = /** @type {Error & { pos: number, end: number }} */ (new Error(message));
|
|
1745
|
-
error.pos = node.start ?? 0;
|
|
1746
|
-
error.end = node.end ?? error.pos + 1;
|
|
1747
|
-
return error;
|
|
1748
|
-
}
|
|
1749
|
-
|
|
1750
|
-
/**
|
|
1751
|
-
* @param {any[]} consequent
|
|
1752
|
-
* @returns {any[]}
|
|
1753
|
-
*/
|
|
1754
|
-
function flatten_switch_consequent(consequent) {
|
|
1755
|
-
const result = [];
|
|
1756
|
-
for (const node of consequent) {
|
|
1757
|
-
if (node.type === 'BlockStatement') result.push(...node.body);
|
|
1758
|
-
else result.push(node);
|
|
1759
|
-
}
|
|
1760
|
-
return result;
|
|
1761
|
-
}
|
|
1762
|
-
|
|
1763
|
-
/**
|
|
1764
|
-
* @param {any} node
|
|
1765
|
-
* @returns {any}
|
|
1766
|
-
*/
|
|
1767
|
-
function tsx_compat_node_to_jsx_expression(node) {
|
|
1500
|
+
function tsx_compat_node_to_jsx_expression(node, in_jsx_child = false) {
|
|
1768
1501
|
if (node.kind !== 'solid') {
|
|
1769
1502
|
throw create_compile_error(
|
|
1770
1503
|
node,
|
|
1771
1504
|
`Solid TSRX does not support <tsx:${node.kind}> blocks. Use <tsx> or <tsx:solid>.`,
|
|
1772
1505
|
);
|
|
1773
1506
|
}
|
|
1774
|
-
return tsx_node_to_jsx_expression(node);
|
|
1775
|
-
}
|
|
1776
|
-
|
|
1777
|
-
/**
|
|
1778
|
-
* `<tsx>...</tsx>` → Solid JSX fragment (or single child if only one).
|
|
1779
|
-
*
|
|
1780
|
-
* @param {any} node
|
|
1781
|
-
* @returns {any}
|
|
1782
|
-
*/
|
|
1783
|
-
function tsx_node_to_jsx_expression(node) {
|
|
1784
|
-
const children = (node.children || []).filter(
|
|
1785
|
-
(/** @type {any} */ child) => child.type !== 'JSXText' || child.value.trim() !== '',
|
|
1786
|
-
);
|
|
1787
|
-
|
|
1788
|
-
if (children.length === 1 && children[0].type !== 'JSXText') {
|
|
1789
|
-
return strip_locations(children[0]);
|
|
1790
|
-
}
|
|
1791
|
-
|
|
1792
|
-
return strip_locations(
|
|
1793
|
-
/** @type {any} */ ({
|
|
1794
|
-
type: 'JSXFragment',
|
|
1795
|
-
openingFragment: { type: 'JSXOpeningFragment', metadata: { path: [] } },
|
|
1796
|
-
closingFragment: { type: 'JSXClosingFragment', metadata: { path: [] } },
|
|
1797
|
-
children,
|
|
1798
|
-
metadata: { path: [] },
|
|
1799
|
-
}),
|
|
1800
|
-
);
|
|
1801
|
-
}
|
|
1802
|
-
|
|
1803
|
-
/**
|
|
1804
|
-
* @param {any} node
|
|
1805
|
-
* @returns {any}
|
|
1806
|
-
*/
|
|
1807
|
-
function strip_locations(node) {
|
|
1808
|
-
if (!node || typeof node !== 'object') return node;
|
|
1809
|
-
if (Array.isArray(node)) return node.map(strip_locations);
|
|
1810
|
-
delete node.loc;
|
|
1811
|
-
delete node.start;
|
|
1812
|
-
delete node.end;
|
|
1813
|
-
for (const key of Object.keys(node)) {
|
|
1814
|
-
if (key === 'metadata') continue;
|
|
1815
|
-
node[key] = strip_locations(node[key]);
|
|
1816
|
-
}
|
|
1817
|
-
return node;
|
|
1507
|
+
return tsx_node_to_jsx_expression(node, in_jsx_child);
|
|
1818
1508
|
}
|