@tsrx/core 0.1.27 → 0.1.28
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/plugin.js +28 -9
- package/src/transform/jsx/helpers.js +7 -8
- package/src/transform/jsx/index.js +126 -10
- package/src/utils/builders.js +38 -0
- package/types/index.d.ts +19 -1
package/package.json
CHANGED
package/src/plugin.js
CHANGED
|
@@ -547,7 +547,7 @@ export function TSRXPlugin(config) {
|
|
|
547
547
|
let index = start;
|
|
548
548
|
let value = '';
|
|
549
549
|
while (index < this.input.length) {
|
|
550
|
-
if (this.#isTemplateLineCommentStart(index)) {
|
|
550
|
+
if (this.#isTemplateLineCommentStart(index, start)) {
|
|
551
551
|
const comment_start = index;
|
|
552
552
|
const comment_start_loc = acorn.getLineInfo(this.input, comment_start);
|
|
553
553
|
index += 2;
|
|
@@ -600,6 +600,7 @@ export function TSRXPlugin(config) {
|
|
|
600
600
|
ch === CharCode.lessThan ||
|
|
601
601
|
ch === CharCode.openBrace ||
|
|
602
602
|
ch === CharCode.closeBrace ||
|
|
603
|
+
this.#isCodeBlockStart(index) ||
|
|
603
604
|
this.#isJSXControlFlowDirectiveAt(index)
|
|
604
605
|
) {
|
|
605
606
|
break;
|
|
@@ -961,14 +962,29 @@ export function TSRXPlugin(config) {
|
|
|
961
962
|
}
|
|
962
963
|
|
|
963
964
|
/**
|
|
965
|
+
* A `//` is a comment only when nothing but whitespace precedes it on its
|
|
966
|
+
* line, or — given `run_start`, the position where the current text run
|
|
967
|
+
* began (right after a sibling element, code block, or expression
|
|
968
|
+
* container) — since that boundary. Once real text has begun, `//` is
|
|
969
|
+
* literal so inline text like `https://…` stays text.
|
|
964
970
|
* @param {number} index
|
|
971
|
+
* @param {number} [run_start]
|
|
965
972
|
*/
|
|
966
|
-
#isTemplateLineCommentStart(index) {
|
|
967
|
-
|
|
968
|
-
this.input.charCodeAt(index)
|
|
969
|
-
this.input.charCodeAt(index + 1)
|
|
970
|
-
|
|
971
|
-
|
|
973
|
+
#isTemplateLineCommentStart(index, run_start = -1) {
|
|
974
|
+
if (
|
|
975
|
+
this.input.charCodeAt(index) !== CharCode.slash ||
|
|
976
|
+
this.input.charCodeAt(index + 1) !== CharCode.slash
|
|
977
|
+
) {
|
|
978
|
+
return false;
|
|
979
|
+
}
|
|
980
|
+
if (this.#isLineStartPosition(index)) return true;
|
|
981
|
+
if (run_start < 0) return false;
|
|
982
|
+
for (let i = index - 1; i >= run_start; i--) {
|
|
983
|
+
const ch = this.input.charCodeAt(i);
|
|
984
|
+
if (ch === CharCode.lineFeed || ch === CharCode.carriageReturn) return false;
|
|
985
|
+
if (ch !== CharCode.space && ch !== CharCode.tab) return false;
|
|
986
|
+
}
|
|
987
|
+
return true;
|
|
972
988
|
}
|
|
973
989
|
|
|
974
990
|
/**
|
|
@@ -996,7 +1012,7 @@ export function TSRXPlugin(config) {
|
|
|
996
1012
|
ch === CharCode.openBrace ||
|
|
997
1013
|
ch === CharCode.closeBrace ||
|
|
998
1014
|
this.#isJSXControlFlowDirectiveAt(index) ||
|
|
999
|
-
this.#isTemplateLineCommentStart(index) ||
|
|
1015
|
+
this.#isTemplateLineCommentStart(index, start) ||
|
|
1000
1016
|
this.#isTemplateBlockCommentStart(index)
|
|
1001
1017
|
) {
|
|
1002
1018
|
break;
|
|
@@ -2714,7 +2730,10 @@ export function TSRXPlugin(config) {
|
|
|
2714
2730
|
this.pos++;
|
|
2715
2731
|
return this.finishToken(tt.arrow);
|
|
2716
2732
|
}
|
|
2717
|
-
if (code === CharCode.lessThan) {
|
|
2733
|
+
if (code === CharCode.lessThan && this.type !== tstt.jsxText) {
|
|
2734
|
+
// After a JSX text token a `<` can only open a tag; without this guard
|
|
2735
|
+
// text ending in an identifier character (`hello<div>`) would read as
|
|
2736
|
+
// the start of a type argument list (`hello<T>`).
|
|
2718
2737
|
const next = this.input.charCodeAt(this.pos + 1);
|
|
2719
2738
|
if (
|
|
2720
2739
|
next !== CharCode.slash &&
|
|
@@ -4,20 +4,19 @@
|
|
|
4
4
|
import tsx from 'esrap/languages/tsx';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Zimmerframe provides `path` as the ancestor chain. A native template node
|
|
8
|
-
*
|
|
9
|
-
* renders as a standalone expression (e.g. a return value).
|
|
7
|
+
* Zimmerframe provides `path` as the ancestor chain. A native template node in
|
|
8
|
+
* the children list of any JSX element/fragment renders as a JSX child;
|
|
9
|
+
* anywhere else it renders as a standalone expression (e.g. a return value).
|
|
10
|
+
* The parent may be a parsed native template node or a synthetic fragment the
|
|
11
|
+
* transform built around render children — either way a bare expression in a
|
|
12
|
+
* child slot would print as JSX text.
|
|
10
13
|
*
|
|
11
14
|
* @param {any[]} path
|
|
12
15
|
* @returns {boolean}
|
|
13
16
|
*/
|
|
14
17
|
export function in_jsx_child_context(path) {
|
|
15
18
|
const parent = path[path.length - 1];
|
|
16
|
-
return (
|
|
17
|
-
!!parent &&
|
|
18
|
-
(parent.type === 'JSXElement' || parent.type === 'JSXFragment') &&
|
|
19
|
-
parent.metadata?.native_tsrx
|
|
20
|
-
);
|
|
19
|
+
return !!parent && (parent.type === 'JSXElement' || parent.type === 'JSXFragment');
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
/**
|
|
@@ -146,10 +146,57 @@ function mark_nested_function_return_jsx(node, inside_function = false, seen = n
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
/**
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
149
|
+
* Lower a `@{ … }` code block that appears as an element/fragment child,
|
|
150
|
+
* paying only for what the block uses while keeping each block its own
|
|
151
|
+
* lexical scope:
|
|
152
|
+
*
|
|
153
|
+
* - no setup code: the scope is unobservable, so the render output merges
|
|
154
|
+
* directly into the children list (template-only chains collapse to the
|
|
155
|
+
* innermost output, empty chains to nothing);
|
|
156
|
+
* - code-only: a plain `{ … }` statement block — statements run in source
|
|
157
|
+
* order, scoped, and render nothing (the render pipeline already handles
|
|
158
|
+
* statements interleaved with JSX children);
|
|
159
|
+
* - setup code + render output: kept as a `JSXCodeBlock` (with any nested
|
|
160
|
+
* chain simplified) for the context-aware lowering into a scoped IIFE
|
|
161
|
+
* (`transform_jsx_code_block` / `build_render_statements`).
|
|
162
|
+
*
|
|
163
|
+
* Always returns zero or one node.
|
|
164
|
+
* @param {any} block
|
|
165
|
+
* @returns {any[]}
|
|
166
|
+
*/
|
|
167
|
+
function lower_code_block_child(block) {
|
|
168
|
+
const body = block.body || [];
|
|
169
|
+
const render = block.render ?? null;
|
|
170
|
+
|
|
171
|
+
if (body.length === 0) {
|
|
172
|
+
if (render == null) return [];
|
|
173
|
+
if (render.type === 'JSXCodeBlock') return lower_code_block_child(render);
|
|
174
|
+
return [render];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (render?.type === 'JSXCodeBlock') {
|
|
178
|
+
const inner = lower_code_block_child(render);
|
|
179
|
+
if (inner.length === 0) {
|
|
180
|
+
return [b.block(body, block)];
|
|
181
|
+
}
|
|
182
|
+
if (inner[0].type === 'BlockStatement') {
|
|
183
|
+
return [b.block([...body, inner[0]], block)];
|
|
184
|
+
}
|
|
185
|
+
// The chain still renders — simplify the render to the lowered inner
|
|
186
|
+
// node and leave the block for the context-aware lowering.
|
|
187
|
+
return [{ ...block, render: inner[0] }];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (render == null) {
|
|
191
|
+
return [b.block(body, block)];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return [block];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Lower `@{ … }` code blocks that appear as element/fragment children (see
|
|
199
|
+
* `lower_code_block_child`). This is the element-scoped equivalent of
|
|
153
200
|
* `transform_function`'s body lowering — function and arrow bodies are never
|
|
154
201
|
* element children, so they are untouched here.
|
|
155
202
|
* @param {any} node
|
|
@@ -170,9 +217,7 @@ function expand_child_code_blocks(node, seen = new Set()) {
|
|
|
170
217
|
node.children.some((/** @type {any} */ c) => c?.type === 'JSXCodeBlock')
|
|
171
218
|
) {
|
|
172
219
|
node.children = node.children.flatMap((/** @type {any} */ child) =>
|
|
173
|
-
child?.type === 'JSXCodeBlock'
|
|
174
|
-
? [...child.body, ...(child.render != null ? [child.render] : [])]
|
|
175
|
-
: [child],
|
|
220
|
+
child?.type === 'JSXCodeBlock' ? lower_code_block_child(child) : [child],
|
|
176
221
|
);
|
|
177
222
|
}
|
|
178
223
|
|
|
@@ -801,6 +846,79 @@ function build_component_statements(body_nodes, transform_context) {
|
|
|
801
846
|
return build_render_statements(body_nodes, false, transform_context);
|
|
802
847
|
}
|
|
803
848
|
|
|
849
|
+
/**
|
|
850
|
+
* Statements for one `@{ … }` scope level: the setup statements followed by
|
|
851
|
+
* the lowered chain continuation. A nested level that declares anything is
|
|
852
|
+
* kept in a nested plain `{ … }` block, so a whole chain shares a single
|
|
853
|
+
* closure while still scoping each level; the generated `return` exits that
|
|
854
|
+
* closure.
|
|
855
|
+
* @param {any} block
|
|
856
|
+
* @param {TransformContext} transform_context
|
|
857
|
+
* @returns {{ statements: any[], has_render: boolean }}
|
|
858
|
+
*/
|
|
859
|
+
function code_block_scope_statements(block, transform_context) {
|
|
860
|
+
const statements = [...(block.body || [])];
|
|
861
|
+
const render = block.render ?? null;
|
|
862
|
+
|
|
863
|
+
if (render == null) {
|
|
864
|
+
return { statements, has_render: false };
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
if (render.type === 'JSXCodeBlock') {
|
|
868
|
+
const inner = code_block_scope_statements(render, transform_context);
|
|
869
|
+
if (inner.statements.length > 0) {
|
|
870
|
+
if ((render.body || []).length > 0) {
|
|
871
|
+
statements.push(b.block(inner.statements, render));
|
|
872
|
+
} else {
|
|
873
|
+
statements.push(...inner.statements);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
return { statements, has_render: inner.has_render };
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
return {
|
|
880
|
+
statements: [...statements, ...build_render_statements([render], true, transform_context)],
|
|
881
|
+
has_render: true,
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* Lower a `@{ … }` code block that appears in a component/IIFE statement
|
|
887
|
+
* stream, keeping each block its own lexical scope:
|
|
888
|
+
*
|
|
889
|
+
* - no setup code: the scope is unobservable, so the render output (if any)
|
|
890
|
+
* merges directly into the stream;
|
|
891
|
+
* - code-only: a plain `{ … }` statement block;
|
|
892
|
+
* - setup code + render output: a scoped IIFE expression child whose value is
|
|
893
|
+
* the render output, with nested chains folded into the one closure.
|
|
894
|
+
*
|
|
895
|
+
* Always returns zero or one node.
|
|
896
|
+
* @param {any} block
|
|
897
|
+
* @param {TransformContext} transform_context
|
|
898
|
+
* @returns {any[]}
|
|
899
|
+
*/
|
|
900
|
+
function lower_code_block_stream_node(block, transform_context) {
|
|
901
|
+
const body = block.body || [];
|
|
902
|
+
const render = block.render ?? null;
|
|
903
|
+
|
|
904
|
+
if (body.length === 0) {
|
|
905
|
+
if (render == null) return [];
|
|
906
|
+
if (render.type === 'JSXCodeBlock') {
|
|
907
|
+
return lower_code_block_stream_node(render, transform_context);
|
|
908
|
+
}
|
|
909
|
+
return [render];
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
const { statements, has_render } = code_block_scope_statements(block, transform_context);
|
|
913
|
+
|
|
914
|
+
if (!has_render) {
|
|
915
|
+
return [b.block(statements, block)];
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
const iife = b.call(b.arrow([], b.block(statements, block)));
|
|
919
|
+
return [to_jsx_expression_container(iife, block)];
|
|
920
|
+
}
|
|
921
|
+
|
|
804
922
|
/**
|
|
805
923
|
* @param {any[]} body_nodes
|
|
806
924
|
* @param {boolean} return_null_when_empty
|
|
@@ -809,9 +927,7 @@ function build_component_statements(body_nodes, transform_context) {
|
|
|
809
927
|
*/
|
|
810
928
|
function build_render_statements(body_nodes, return_null_when_empty, transform_context) {
|
|
811
929
|
body_nodes = body_nodes.flatMap((node) =>
|
|
812
|
-
node?.type === 'JSXCodeBlock'
|
|
813
|
-
? [...node.body, ...(node.render != null ? [node.render] : [])]
|
|
814
|
-
: [node],
|
|
930
|
+
node?.type === 'JSXCodeBlock' ? lower_code_block_stream_node(node, transform_context) : [node],
|
|
815
931
|
);
|
|
816
932
|
|
|
817
933
|
const statements = [];
|
package/src/utils/builders.js
CHANGED
|
@@ -1249,6 +1249,44 @@ export function jsx_fragment(children = [], attributes = []) {
|
|
|
1249
1249
|
};
|
|
1250
1250
|
}
|
|
1251
1251
|
|
|
1252
|
+
/**
|
|
1253
|
+
* Ripple's internal fragment template node (the normalized form of a
|
|
1254
|
+
* `JSXFragment`).
|
|
1255
|
+
* @param {AST.Node[]} [children]
|
|
1256
|
+
* @param {AST.NodeWithLocation} [loc_info]
|
|
1257
|
+
* @returns {AST.TsrxFragment}
|
|
1258
|
+
*/
|
|
1259
|
+
export function tsrx_fragment(children = [], loc_info) {
|
|
1260
|
+
const node = /** @type {AST.TsrxFragment} */ (
|
|
1261
|
+
/** @type {unknown} */ ({
|
|
1262
|
+
type: 'TsrxFragment',
|
|
1263
|
+
children,
|
|
1264
|
+
attributes: [],
|
|
1265
|
+
selfClosing: false,
|
|
1266
|
+
metadata: { path: [] },
|
|
1267
|
+
})
|
|
1268
|
+
);
|
|
1269
|
+
|
|
1270
|
+
return set_location(node, loc_info);
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
/**
|
|
1274
|
+
* Ripple's internal expression template child (the normalized form of a
|
|
1275
|
+
* `JSXExpressionContainer` child).
|
|
1276
|
+
* @param {AST.Expression} expression
|
|
1277
|
+
* @param {AST.NodeWithLocation} [loc_info]
|
|
1278
|
+
* @returns {AST.TSRXExpression}
|
|
1279
|
+
*/
|
|
1280
|
+
export function tsrx_expression(expression, loc_info) {
|
|
1281
|
+
const node = /** @type {AST.TSRXExpression} */ ({
|
|
1282
|
+
type: 'TSRXExpression',
|
|
1283
|
+
expression,
|
|
1284
|
+
metadata: { path: [] },
|
|
1285
|
+
});
|
|
1286
|
+
|
|
1287
|
+
return set_location(node, loc_info);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1252
1290
|
/**
|
|
1253
1291
|
* @param {AST.Expression | ESTreeJSX.JSXEmptyExpression} expression
|
|
1254
1292
|
* @param {AST.NodeWithLocation} [loc_info]
|
package/types/index.d.ts
CHANGED
|
@@ -170,6 +170,17 @@ declare module 'estree' {
|
|
|
170
170
|
interface SimpleCallExpression {
|
|
171
171
|
metadata: BaseNodeMetaData & {
|
|
172
172
|
hash?: string;
|
|
173
|
+
/**
|
|
174
|
+
* A generated `(() => @{ … })()` inline-component IIFE for a code
|
|
175
|
+
* block; collapsible once the block's statements lower into the
|
|
176
|
+
* component callback.
|
|
177
|
+
*/
|
|
178
|
+
tsrx_code_block_component?: boolean;
|
|
179
|
+
/**
|
|
180
|
+
* A generated zero-argument scope IIFE for a `@{ … }` code-block
|
|
181
|
+
* chain level; runs synchronously inside its `with_scope` wrapper.
|
|
182
|
+
*/
|
|
183
|
+
tsrx_code_block_scope?: boolean;
|
|
173
184
|
};
|
|
174
185
|
}
|
|
175
186
|
|
|
@@ -323,7 +334,14 @@ declare module 'estree' {
|
|
|
323
334
|
closingElement?: ESTreeJSX.JSXClosingFragment | null;
|
|
324
335
|
selfClosing?: boolean;
|
|
325
336
|
attributes?: Array<Attribute | SpreadAttribute>;
|
|
326
|
-
metadata: BaseNodeMetaData
|
|
337
|
+
metadata: BaseNodeMetaData & {
|
|
338
|
+
/**
|
|
339
|
+
* A synthetic wrapper for a nested code-block render chain
|
|
340
|
+
* (`@{ @{ … } }`), so render-slot consumers see a template node;
|
|
341
|
+
* template-children lowering unwraps it.
|
|
342
|
+
*/
|
|
343
|
+
tsrx_code_block_chain?: boolean;
|
|
344
|
+
};
|
|
327
345
|
start: number;
|
|
328
346
|
end: number;
|
|
329
347
|
}
|