@tsrx/core 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 +4 -2
- package/src/index.js +22 -0
- package/src/plugin.js +39 -8
- package/src/source-map-utils.js +4 -2
- package/src/transform/jsx/ast-builders.js +321 -0
- package/src/transform/jsx/helpers.js +131 -0
- package/src/transform/jsx/index.js +2486 -0
- package/src/transform/scoping.js +120 -8
- package/src/transform/segments.js +37 -46
- package/types/index.d.ts +24 -10
- package/types/jsx-platform.d.ts +193 -0
- package/types/parse.d.ts +5 -6
package/src/transform/scoping.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Framework-agnostic CSS scoping utilities shared between the `@tsrx/react`
|
|
3
3
|
* and `@tsrx/solid` transforms. These walk the template AST and annotate
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* template nodes with a hash class so scope-qualified selectors (e.g.
|
|
5
|
+
* `.foo.hash`) match after rendering.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { walk } from 'zimmerframe';
|
|
@@ -61,15 +61,42 @@ export function is_composite_element(node) {
|
|
|
61
61
|
return node.id.type === 'MemberExpression';
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* @param {any} node
|
|
66
|
+
* @returns {boolean}
|
|
67
|
+
*/
|
|
68
|
+
function is_style_jsx_element(node) {
|
|
69
|
+
const name = node?.openingElement?.name;
|
|
70
|
+
return node?.type === 'JSXElement' && name?.type === 'JSXIdentifier' && name.name === 'style';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {any} node
|
|
75
|
+
* @returns {boolean}
|
|
76
|
+
*/
|
|
77
|
+
function is_composite_jsx_element(node) {
|
|
78
|
+
const name = node?.openingElement?.name;
|
|
79
|
+
if (node?.type !== 'JSXElement' || !name) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (name.type === 'JSXIdentifier') {
|
|
84
|
+
return /^[A-Z]/.test(name.name);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return name.type === 'JSXMemberExpression';
|
|
88
|
+
}
|
|
89
|
+
|
|
64
90
|
/**
|
|
65
91
|
* Recursively walk `Element` nodes within a component body and add the hash
|
|
66
92
|
* class name so scope-qualified selectors (e.g. `.foo.hash`) match.
|
|
67
93
|
*
|
|
68
94
|
* @param {any} node
|
|
69
95
|
* @param {string} hash
|
|
96
|
+
* @param {'class' | 'className'} [jsx_class_attr_name='class']
|
|
70
97
|
* @returns {any}
|
|
71
98
|
*/
|
|
72
|
-
export function annotate_with_hash(node, hash) {
|
|
99
|
+
export function annotate_with_hash(node, hash, jsx_class_attr_name = 'class') {
|
|
73
100
|
if (!node || typeof node !== 'object') return node;
|
|
74
101
|
if (
|
|
75
102
|
node.type === 'Component' ||
|
|
@@ -87,7 +114,19 @@ export function annotate_with_hash(node, hash) {
|
|
|
87
114
|
if (Array.isArray(node.children)) {
|
|
88
115
|
node.children = node.children
|
|
89
116
|
.filter((/** @type {any} */ child) => !is_style_element(child))
|
|
90
|
-
.map((/** @type {any} */ child) => annotate_with_hash(child, hash));
|
|
117
|
+
.map((/** @type {any} */ child) => annotate_with_hash(child, hash, jsx_class_attr_name));
|
|
118
|
+
}
|
|
119
|
+
return node;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (node.type === 'JSXElement') {
|
|
123
|
+
if (!is_style_jsx_element(node) && !is_composite_jsx_element(node)) {
|
|
124
|
+
add_hash_class_to_jsx_element(node, hash, jsx_class_attr_name);
|
|
125
|
+
}
|
|
126
|
+
if (Array.isArray(node.children)) {
|
|
127
|
+
node.children = node.children.map((/** @type {any} */ child) =>
|
|
128
|
+
annotate_with_hash(child, hash, jsx_class_attr_name),
|
|
129
|
+
);
|
|
91
130
|
}
|
|
92
131
|
return node;
|
|
93
132
|
}
|
|
@@ -99,9 +138,11 @@ export function annotate_with_hash(node, hash) {
|
|
|
99
138
|
|
|
100
139
|
const value = node[key];
|
|
101
140
|
if (Array.isArray(value)) {
|
|
102
|
-
node[key] = value.map((/** @type {any} */ child) =>
|
|
141
|
+
node[key] = value.map((/** @type {any} */ child) =>
|
|
142
|
+
annotate_with_hash(child, hash, jsx_class_attr_name),
|
|
143
|
+
);
|
|
103
144
|
} else if (value && typeof value === 'object') {
|
|
104
|
-
node[key] = annotate_with_hash(value, hash);
|
|
145
|
+
node[key] = annotate_with_hash(value, hash, jsx_class_attr_name);
|
|
105
146
|
}
|
|
106
147
|
}
|
|
107
148
|
|
|
@@ -111,14 +152,15 @@ export function annotate_with_hash(node, hash) {
|
|
|
111
152
|
/**
|
|
112
153
|
* @param {any} component
|
|
113
154
|
* @param {string} hash
|
|
155
|
+
* @param {'class' | 'className'} [jsx_class_attr_name='class']
|
|
114
156
|
* @returns {void}
|
|
115
157
|
*/
|
|
116
|
-
export function annotate_component_with_hash(component, hash) {
|
|
158
|
+
export function annotate_component_with_hash(component, hash, jsx_class_attr_name = 'class') {
|
|
117
159
|
/** @type {any[]} */
|
|
118
160
|
const body = component.body;
|
|
119
161
|
component.body = body
|
|
120
162
|
.filter((/** @type {any} */ child) => !is_style_element(child))
|
|
121
|
-
.map((/** @type {any} */ child) => annotate_with_hash(child, hash));
|
|
163
|
+
.map((/** @type {any} */ child) => annotate_with_hash(child, hash, jsx_class_attr_name));
|
|
122
164
|
}
|
|
123
165
|
|
|
124
166
|
/**
|
|
@@ -178,3 +220,73 @@ export function add_hash_class(element, hash) {
|
|
|
178
220
|
],
|
|
179
221
|
};
|
|
180
222
|
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* @param {any} element
|
|
226
|
+
* @param {string} hash
|
|
227
|
+
* @param {'class' | 'className'} jsx_class_attr_name
|
|
228
|
+
* @returns {void}
|
|
229
|
+
*/
|
|
230
|
+
function add_hash_class_to_jsx_element(element, hash, jsx_class_attr_name) {
|
|
231
|
+
const attrs = element.openingElement?.attributes || (element.openingElement.attributes = []);
|
|
232
|
+
const existing = attrs.find(
|
|
233
|
+
(/** @type {any} */ attr) =>
|
|
234
|
+
attr?.type === 'JSXAttribute' &&
|
|
235
|
+
attr.name?.type === 'JSXIdentifier' &&
|
|
236
|
+
(attr.name.name === 'class' || attr.name.name === 'className'),
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
if (!existing) {
|
|
240
|
+
attrs.push({
|
|
241
|
+
type: 'JSXAttribute',
|
|
242
|
+
name: { type: 'JSXIdentifier', name: jsx_class_attr_name, metadata: { path: [] } },
|
|
243
|
+
value: { type: 'Literal', value: hash, raw: JSON.stringify(hash) },
|
|
244
|
+
shorthand: false,
|
|
245
|
+
metadata: { path: [] },
|
|
246
|
+
});
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (existing.name.name !== jsx_class_attr_name) {
|
|
251
|
+
existing.name = {
|
|
252
|
+
type: 'JSXIdentifier',
|
|
253
|
+
name: jsx_class_attr_name,
|
|
254
|
+
metadata: existing.name.metadata || { path: [] },
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const value = existing.value;
|
|
259
|
+
if (!value) {
|
|
260
|
+
existing.value = { type: 'Literal', value: hash, raw: JSON.stringify(hash) };
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (value.type === 'Literal' && typeof value.value === 'string') {
|
|
265
|
+
const merged = `${value.value} ${hash}`;
|
|
266
|
+
existing.value = { type: 'Literal', value: merged, raw: JSON.stringify(merged) };
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const expression = value.type === 'JSXExpressionContainer' ? value.expression : value;
|
|
271
|
+
existing.value = {
|
|
272
|
+
type: 'JSXExpressionContainer',
|
|
273
|
+
expression: {
|
|
274
|
+
type: 'TemplateLiteral',
|
|
275
|
+
expressions: [expression],
|
|
276
|
+
quasis: [
|
|
277
|
+
{
|
|
278
|
+
type: 'TemplateElement',
|
|
279
|
+
value: { raw: '', cooked: '' },
|
|
280
|
+
tail: false,
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
type: 'TemplateElement',
|
|
284
|
+
value: { raw: ` ${hash}`, cooked: ` ${hash}` },
|
|
285
|
+
tail: true,
|
|
286
|
+
},
|
|
287
|
+
],
|
|
288
|
+
metadata: { path: [] },
|
|
289
|
+
},
|
|
290
|
+
metadata: { path: [] },
|
|
291
|
+
};
|
|
292
|
+
}
|
|
@@ -52,7 +52,6 @@ import {
|
|
|
52
52
|
mapping_data_verify_complete,
|
|
53
53
|
build_line_offsets,
|
|
54
54
|
get_mapping_from_node,
|
|
55
|
-
maybe_get_mapping_from_node,
|
|
56
55
|
} from '../source-map-utils.js';
|
|
57
56
|
|
|
58
57
|
const LABEL_TO_COMPONENT_REPLACE_REGEX = /(function|\((property|method)\))/;
|
|
@@ -670,21 +669,9 @@ export function convert_source_map_to_mappings(
|
|
|
670
669
|
return;
|
|
671
670
|
} else if (node.type === 'JSXExpressionContainer') {
|
|
672
671
|
if (node.loc) {
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
// construct (e.g. a Ripple TSRXExpression or Text node), while
|
|
676
|
-
// esrap only emits a segment for the inner expression. In that
|
|
677
|
-
// case the container's start/end won't resolve — skip rather
|
|
678
|
-
// than hard-failing, and rely on the inner expression's mapping.
|
|
679
|
-
const mapping = maybe_get_mapping_from_node(
|
|
680
|
-
node,
|
|
681
|
-
src_to_gen_map,
|
|
682
|
-
gen_line_offsets,
|
|
683
|
-
mapping_data_verify_only,
|
|
672
|
+
mappings.push(
|
|
673
|
+
get_mapping_from_node(node, src_to_gen_map, gen_line_offsets, mapping_data_verify_only),
|
|
684
674
|
);
|
|
685
|
-
if (!(mapping instanceof Error)) {
|
|
686
|
-
mappings.push(mapping);
|
|
687
|
-
}
|
|
688
675
|
}
|
|
689
676
|
// Visit the expression inside {}
|
|
690
677
|
if (node.expression) {
|
|
@@ -742,36 +729,22 @@ export function convert_source_map_to_mappings(
|
|
|
742
729
|
|
|
743
730
|
if ((closing?.loc || opening.loc) && (closing || opening.selfClosing)) {
|
|
744
731
|
// Add the whole closing tag or the self-closing.
|
|
745
|
-
// For self-closing elements, use maybe_get_mapping_from_node because
|
|
746
|
-
// attribute transforms (e.g. class→className, {ref fn}→ref={fn}) can shift
|
|
747
|
-
// the position of `/>` in the generated output, making the source map
|
|
748
|
-
// entry for the opening element's end position unresolvable.
|
|
749
732
|
const target_node = closing ? closing : opening;
|
|
750
|
-
const mapping =
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
// The generated code includes a semicolon after the closing or self-closed tag
|
|
766
|
-
// We're extending the mapping to include the semicolon
|
|
767
|
-
// because the diagnostics errors can include the whole element
|
|
768
|
-
// and we need to account for the semicolon as it's a part of the diagnostic
|
|
769
|
-
// At the same time, we could've instead applied this logic to the whole `node` element
|
|
770
|
-
// but since we already map the opening - start, we just need the proper end
|
|
771
|
-
// and it was causing some issues with mappings
|
|
772
|
-
mapping.generatedLengths = [mapping.generatedLengths[0] + 1];
|
|
773
|
-
mappings.push(mapping);
|
|
774
|
-
}
|
|
733
|
+
const mapping = get_mapping_from_node(
|
|
734
|
+
target_node,
|
|
735
|
+
src_to_gen_map,
|
|
736
|
+
gen_line_offsets,
|
|
737
|
+
mapping_data_verify_only,
|
|
738
|
+
);
|
|
739
|
+
// The generated code includes a semicolon after the closing or self-closed tag
|
|
740
|
+
// We're extending the mapping to include the semicolon
|
|
741
|
+
// because the diagnostics errors can include the whole element
|
|
742
|
+
// and we need to account for the semicolon as it's a part of the diagnostic
|
|
743
|
+
// At the same time, we could've instead applied this logic to the whole `node` element
|
|
744
|
+
// but since we already map the opening - start, we just need the proper end
|
|
745
|
+
// and it was causing some issues with mappings
|
|
746
|
+
mapping.generatedLengths = [mapping.generatedLengths[0] + 1];
|
|
747
|
+
mappings.push(mapping);
|
|
775
748
|
}
|
|
776
749
|
|
|
777
750
|
if (closing) {
|
|
@@ -1618,9 +1591,18 @@ export function convert_source_map_to_mappings(
|
|
|
1618
1591
|
node.type === 'TSTypeParameterDeclaration'
|
|
1619
1592
|
) {
|
|
1620
1593
|
if (node.loc) {
|
|
1621
|
-
|
|
1622
|
-
|
|
1594
|
+
// Generic spans can be emitted by downstream transforms with sparse source-map
|
|
1595
|
+
// coverage around the angle-bracket delimiters. Skip missing whole-node mappings
|
|
1596
|
+
// instead of crashing Volar, and rely on child type-node mappings instead.
|
|
1597
|
+
const mapping = get_mapping_from_node(
|
|
1598
|
+
node,
|
|
1599
|
+
src_to_gen_map,
|
|
1600
|
+
gen_line_offsets,
|
|
1601
|
+
mapping_data_verify_only,
|
|
1623
1602
|
);
|
|
1603
|
+
if (!(mapping instanceof Error)) {
|
|
1604
|
+
mappings.push(mapping);
|
|
1605
|
+
}
|
|
1624
1606
|
}
|
|
1625
1607
|
// Generic type parameters - visit to collect type variable names
|
|
1626
1608
|
if (node.params) {
|
|
@@ -2061,6 +2043,15 @@ export function convert_source_map_to_mappings(
|
|
|
2061
2043
|
visit(node.typeArguments);
|
|
2062
2044
|
}
|
|
2063
2045
|
return;
|
|
2046
|
+
} else if (node.type === 'TSTypePredicate') {
|
|
2047
|
+
// Type predicate: `x is T` / `asserts x is T` / `asserts x`
|
|
2048
|
+
if (node.parameterName) {
|
|
2049
|
+
visit(node.parameterName);
|
|
2050
|
+
}
|
|
2051
|
+
if (node.typeAnnotation) {
|
|
2052
|
+
visit(node.typeAnnotation);
|
|
2053
|
+
}
|
|
2054
|
+
return;
|
|
2064
2055
|
}
|
|
2065
2056
|
|
|
2066
2057
|
throw new Error(`Unhandled AST node type in mapping walker: ${node.type}`);
|
package/types/index.d.ts
CHANGED
|
@@ -5,8 +5,16 @@ import type { Parse } from './parse.js';
|
|
|
5
5
|
import type * as ESRap from 'esrap';
|
|
6
6
|
import type { Position } from 'acorn';
|
|
7
7
|
import type { RequireAllOrNone } from '../src/helpers.js';
|
|
8
|
+
import type {
|
|
9
|
+
JsxPlatform,
|
|
10
|
+
JsxPlatformHooks,
|
|
11
|
+
JsxTransformOptions,
|
|
12
|
+
JsxTransformResult,
|
|
13
|
+
createJsxTransform,
|
|
14
|
+
} from './jsx-platform';
|
|
8
15
|
|
|
9
|
-
export type { Parse };
|
|
16
|
+
export type { Parse, JsxPlatform, JsxPlatformHooks, JsxTransformOptions, JsxTransformResult };
|
|
17
|
+
export { createJsxTransform };
|
|
10
18
|
|
|
11
19
|
/**
|
|
12
20
|
* Compile error interface
|
|
@@ -76,7 +84,7 @@ interface FunctionLikeTS {
|
|
|
76
84
|
typeAnnotation?: AST.TSTypeAnnotation;
|
|
77
85
|
}
|
|
78
86
|
|
|
79
|
-
//
|
|
87
|
+
// TSRX augmentation for ESTree function nodes
|
|
80
88
|
declare module 'estree' {
|
|
81
89
|
interface Program {
|
|
82
90
|
innerComments?: Comment[] | undefined;
|
|
@@ -164,7 +172,7 @@ declare module 'estree' {
|
|
|
164
172
|
was_expression?: boolean;
|
|
165
173
|
}
|
|
166
174
|
|
|
167
|
-
// Include TypeScript node types and
|
|
175
|
+
// Include TypeScript node types and TSRX-specific nodes in NodeMap
|
|
168
176
|
interface NodeMap {
|
|
169
177
|
Component: Component;
|
|
170
178
|
Tsx: Tsx;
|
|
@@ -291,7 +299,7 @@ declare module 'estree' {
|
|
|
291
299
|
}
|
|
292
300
|
|
|
293
301
|
/**
|
|
294
|
-
*
|
|
302
|
+
* TSRX custom interfaces and types section
|
|
295
303
|
*/
|
|
296
304
|
interface Component extends AST.BaseNode {
|
|
297
305
|
type: 'Component';
|
|
@@ -398,7 +406,7 @@ declare module 'estree' {
|
|
|
398
406
|
}
|
|
399
407
|
|
|
400
408
|
/**
|
|
401
|
-
*
|
|
409
|
+
* TSRX attribute nodes
|
|
402
410
|
*/
|
|
403
411
|
interface Attribute extends AST.BaseNode {
|
|
404
412
|
type: 'Attribute';
|
|
@@ -424,20 +432,20 @@ declare module 'estree' {
|
|
|
424
432
|
}
|
|
425
433
|
|
|
426
434
|
/**
|
|
427
|
-
*
|
|
435
|
+
* TSRX's extended Declaration type that includes Component
|
|
428
436
|
* Use this instead of Declaration when you need Component support
|
|
429
437
|
*/
|
|
430
438
|
export type TSRXDeclaration = AST.Declaration | Component | AST.TSDeclareFunction;
|
|
431
439
|
|
|
432
440
|
/**
|
|
433
|
-
*
|
|
441
|
+
* TSRX's extended ExportNamedDeclaration with Component support
|
|
434
442
|
*/
|
|
435
443
|
interface TSRXExportNamedDeclaration extends Omit<AST.ExportNamedDeclaration, 'declaration'> {
|
|
436
444
|
declaration?: TSRXDeclaration | null | undefined;
|
|
437
445
|
}
|
|
438
446
|
|
|
439
447
|
/**
|
|
440
|
-
*
|
|
448
|
+
* TSRX's extended Program with Component support
|
|
441
449
|
*/
|
|
442
450
|
interface TSRXProgram extends Omit<Program, 'body'> {
|
|
443
451
|
body: (Program['body'][number] | Component | FunctionExpression)[];
|
|
@@ -1046,7 +1054,13 @@ declare module 'estree' {
|
|
|
1046
1054
|
> {
|
|
1047
1055
|
params: TypeNode[];
|
|
1048
1056
|
}
|
|
1049
|
-
interface TSTypePredicate extends
|
|
1057
|
+
interface TSTypePredicate extends Omit<
|
|
1058
|
+
AcornTSNode<TSESTree.TSTypePredicate>,
|
|
1059
|
+
'parameterName' | 'typeAnnotation'
|
|
1060
|
+
> {
|
|
1061
|
+
parameterName: AST.Identifier | AST.TSThisType;
|
|
1062
|
+
typeAnnotation: AST.TSTypeAnnotation | null;
|
|
1063
|
+
}
|
|
1050
1064
|
interface TSTypeQuery extends Omit<
|
|
1051
1065
|
AcornTSNode<TSESTree.TSTypeQuery>,
|
|
1052
1066
|
'exprName' | 'typeArguments'
|
|
@@ -1148,7 +1162,7 @@ export interface AnalysisResult {
|
|
|
1148
1162
|
}
|
|
1149
1163
|
|
|
1150
1164
|
/**
|
|
1151
|
-
* Configuration for
|
|
1165
|
+
* Configuration for the TSRX parser plugin
|
|
1152
1166
|
*/
|
|
1153
1167
|
export interface TSRXPluginConfig {
|
|
1154
1168
|
allowSatisfies?: boolean;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import type * as AST from 'estree';
|
|
2
|
+
import type { RawSourceMap } from 'source-map';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Result returned by a JSX platform transform (React, Preact, Solid).
|
|
6
|
+
*/
|
|
7
|
+
export interface JsxTransformResult {
|
|
8
|
+
ast: AST.Program;
|
|
9
|
+
code: string;
|
|
10
|
+
/**
|
|
11
|
+
* Esrap-shaped source map over the generated TSX. Consumed by
|
|
12
|
+
* `create_volar_mappings_result` to build Volar code mappings and by
|
|
13
|
+
* downstream Vite / Rollup plugins to chain source maps.
|
|
14
|
+
*/
|
|
15
|
+
map: RawSourceMap;
|
|
16
|
+
css: { code: string; hash: string } | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Optional per-call compile options passed to a created JSX transform.
|
|
21
|
+
*/
|
|
22
|
+
export interface JsxTransformOptions {
|
|
23
|
+
/**
|
|
24
|
+
* Override the import source used for `Suspense` in try-block transforms.
|
|
25
|
+
* Falls back to `platform.imports.suspense`. Preact uses this to let the
|
|
26
|
+
* host pick `preact/compat` vs. another compat entry point.
|
|
27
|
+
*/
|
|
28
|
+
suspenseSource?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Override hooks for the parts of the transform that differ substantially
|
|
33
|
+
* between platforms. Every hook is optional — when omitted, the factory
|
|
34
|
+
* uses its React/Preact-style default.
|
|
35
|
+
*
|
|
36
|
+
* Solid uses all of these: control-flow statements become `<Show>` /
|
|
37
|
+
* `<For>` / `<Switch>/<Match>` / `<Errored>/<Loading>` JSX; component
|
|
38
|
+
* bodies are hoisted to preserve setup-once semantics; module imports
|
|
39
|
+
* come from `solid-js` instead of `react`; element attributes support
|
|
40
|
+
* composite-element / textContent shortcuts.
|
|
41
|
+
*
|
|
42
|
+
* The `ctx` parameter is the active `TransformContext` — see the
|
|
43
|
+
* target's transform.js for its shape; platform-owned fields can be
|
|
44
|
+
* read and written freely.
|
|
45
|
+
*/
|
|
46
|
+
export interface JsxPlatformHooks {
|
|
47
|
+
/**
|
|
48
|
+
* Per-statement control-flow rewrites. Each hook receives the original
|
|
49
|
+
* Ripple statement (with children already walked) and returns a JSX
|
|
50
|
+
* child (or an expression container wrapping one).
|
|
51
|
+
*/
|
|
52
|
+
controlFlow?: {
|
|
53
|
+
ifStatement?: (node: any, ctx: any) => any;
|
|
54
|
+
forOf?: (node: any, ctx: any) => any;
|
|
55
|
+
switchStatement?: (node: any, ctx: any) => any;
|
|
56
|
+
tryStatement?: (node: any, ctx: any) => any;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Lower a `component` declaration to a FunctionDeclaration. React / Preact
|
|
60
|
+
* use a default that extracts hooks, hoists statics, and handles async
|
|
61
|
+
* bodies. Solid replaces this with a setup-once implementation that
|
|
62
|
+
* hoists post-early-return JSX into a reactive `<Show>`.
|
|
63
|
+
*/
|
|
64
|
+
componentToFunction?: (component: any, ctx: any, helperState?: any) => any;
|
|
65
|
+
/**
|
|
66
|
+
* Inject module-level imports after the main walk. Default: import
|
|
67
|
+
* `Suspense` from `platform.imports.suspense` and `TsrxErrorBoundary`
|
|
68
|
+
* from `platform.imports.errorBoundary` if the walk flagged them.
|
|
69
|
+
* Solid injects `Show`, `For`, `Switch`, `Match`, `Errored`, `Loading`
|
|
70
|
+
* from `solid-js`.
|
|
71
|
+
*/
|
|
72
|
+
injectImports?: (program: AST.Program, ctx: any, suspenseSource: string) => void;
|
|
73
|
+
/**
|
|
74
|
+
* Transform a Ripple element's attributes to JSX attributes. Default
|
|
75
|
+
* is "map over `to_jsx_attribute`". Solid replaces this to route
|
|
76
|
+
* attributes through its composite-element handling.
|
|
77
|
+
*/
|
|
78
|
+
transformElementAttributes?: (attrs: any[], ctx: any, element: any) => any[];
|
|
79
|
+
/**
|
|
80
|
+
* Lower a Ripple `Element` node to a JSXElement. Default is the
|
|
81
|
+
* factory's `to_jsx_element`. The hook receives the walker-transformed
|
|
82
|
+
* node (`inner`, with children already lowered) plus the element's
|
|
83
|
+
* raw pre-walk children — Solid uses the latter to detect a lone
|
|
84
|
+
* `Text` child it can hoist to a `textContent` attribute before the
|
|
85
|
+
* generic text→JSXExpressionContainer transform runs.
|
|
86
|
+
*/
|
|
87
|
+
transformElement?: (inner: any, ctx: any, rawChildren: any[]) => any;
|
|
88
|
+
/**
|
|
89
|
+
* Custom validation for a component body that uses top-level `await`.
|
|
90
|
+
* Default: enforce `validation.requireUseServerForAwait`. Solid rejects
|
|
91
|
+
* component-level await outright with a keyword-precise location.
|
|
92
|
+
*/
|
|
93
|
+
validateComponentAwait?: (
|
|
94
|
+
awaitNode: any,
|
|
95
|
+
component: any,
|
|
96
|
+
ctx: any,
|
|
97
|
+
moduleUsesServerDirective: boolean,
|
|
98
|
+
source: string,
|
|
99
|
+
) => void;
|
|
100
|
+
/**
|
|
101
|
+
* Factory-managed state extra fields. Returns a record merged into the
|
|
102
|
+
* initial `transform_context`. Lets solid seed its `needs_show` /
|
|
103
|
+
* `needs_for` / etc. flags without forking the factory.
|
|
104
|
+
*/
|
|
105
|
+
initialState?: () => Record<string, unknown>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* A JSX platform descriptor is the parameter to `createJsxTransform`. It
|
|
110
|
+
* declares how to render a Ripple AST as valid TSX for the target platform
|
|
111
|
+
* (React, Preact, Solid). The shared transformer in `@tsrx/core` reads this
|
|
112
|
+
* descriptor at each platform-specific decision point instead of branching
|
|
113
|
+
* on the platform name.
|
|
114
|
+
*/
|
|
115
|
+
export interface JsxPlatform {
|
|
116
|
+
/**
|
|
117
|
+
* Human-readable platform name, used in error messages
|
|
118
|
+
* (e.g. "React TSRX does not support …").
|
|
119
|
+
*/
|
|
120
|
+
name: string;
|
|
121
|
+
|
|
122
|
+
imports: {
|
|
123
|
+
/**
|
|
124
|
+
* Module to import `Suspense` from when a `try { ... } pending { ... }`
|
|
125
|
+
* block appears. React: `'react'`. Preact: `'preact/compat'`.
|
|
126
|
+
*/
|
|
127
|
+
suspense: string;
|
|
128
|
+
/**
|
|
129
|
+
* Module to import `TsrxErrorBoundary` from when a `try { ... } catch (...)`
|
|
130
|
+
* block appears. Usually `'@tsrx/<platform>/error-boundary'`.
|
|
131
|
+
*/
|
|
132
|
+
errorBoundary: string;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
jsx: {
|
|
136
|
+
/**
|
|
137
|
+
* Rewrite Ripple's `class` attribute to React's `className`. React: true.
|
|
138
|
+
* Preact and Solid accept `class` natively, so: false.
|
|
139
|
+
*/
|
|
140
|
+
rewriteClassAttr: boolean;
|
|
141
|
+
/**
|
|
142
|
+
* Accepted values of `kind` in `<tsx:kind>` compat blocks. React accepts
|
|
143
|
+
* only `'react'`. Preact accepts both `'preact'` and `'react'`.
|
|
144
|
+
*/
|
|
145
|
+
acceptedTsxKinds: readonly string[];
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
validation: {
|
|
149
|
+
/**
|
|
150
|
+
* Require a top-level `"use server"` directive before a component may
|
|
151
|
+
* contain top-level `await`. Preact/Solid: true. React: false.
|
|
152
|
+
*
|
|
153
|
+
* Solid keeps this enabled as a fallback invariant (if its custom await
|
|
154
|
+
* validator hook is removed, the default factory validation still rejects
|
|
155
|
+
* component-level `await` without `"use server"`).
|
|
156
|
+
*/
|
|
157
|
+
requireUseServerForAwait: boolean;
|
|
158
|
+
/**
|
|
159
|
+
* When `false`, skip scanning for a top-level `"use server"` directive
|
|
160
|
+
* while a custom `validateComponentAwait` hook is present.
|
|
161
|
+
*
|
|
162
|
+
* This is useful for platforms whose custom validator never uses the
|
|
163
|
+
* directive signal (for example Solid, which always rejects component-level
|
|
164
|
+
* `await`), while still keeping `requireUseServerForAwait: true` as a
|
|
165
|
+
* fallback if the custom validator is removed.
|
|
166
|
+
*
|
|
167
|
+
* Default: `true`.
|
|
168
|
+
*/
|
|
169
|
+
scanUseServerDirectiveForAwaitWithCustomValidator?: boolean;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Optional overrides for parts of the transform that diverge substantially
|
|
174
|
+
* between platforms (control flow, component lowering, imports, element
|
|
175
|
+
* attributes). When absent, each hook falls back to the React/Preact-style
|
|
176
|
+
* default baked into the factory.
|
|
177
|
+
*/
|
|
178
|
+
hooks?: JsxPlatformHooks;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Build a `transform()` function for a specific JSX platform. The returned
|
|
183
|
+
* function takes a parsed Ripple AST and produces a TSX module plus source
|
|
184
|
+
* map and optional CSS.
|
|
185
|
+
*/
|
|
186
|
+
export function createJsxTransform(
|
|
187
|
+
platform: JsxPlatform,
|
|
188
|
+
): (
|
|
189
|
+
ast: AST.Program,
|
|
190
|
+
source: string,
|
|
191
|
+
filename?: string,
|
|
192
|
+
options?: JsxTransformOptions,
|
|
193
|
+
) => JsxTransformResult;
|
package/types/parse.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Type definitions for
|
|
2
|
+
* Type definitions for TSRX's extended Acorn parser
|
|
3
3
|
*
|
|
4
4
|
* These types cover internal properties and static class members that aren't
|
|
5
|
-
* included in Acorn's official type definitions but are used by
|
|
5
|
+
* included in Acorn's official type definitions but are used by the TSRX parser.
|
|
6
6
|
*
|
|
7
7
|
* Based on acorn source code: https://github.com/acornjs/acorn
|
|
8
8
|
* and @sveltejs/acorn-typescript: https://github.com/sveltejs/acorn-typescript
|
|
@@ -433,7 +433,7 @@ export namespace Parse {
|
|
|
433
433
|
* Extended Parser instance with internal properties
|
|
434
434
|
*
|
|
435
435
|
* These properties are used internally by Acorn but not exposed in official types.
|
|
436
|
-
* They are accessed by
|
|
436
|
+
* They are accessed by the TSRX parser plugin for whitespace handling,
|
|
437
437
|
* JSX parsing, and other advanced features.
|
|
438
438
|
*/
|
|
439
439
|
export interface Parser {
|
|
@@ -1652,7 +1652,6 @@ export namespace Parse {
|
|
|
1652
1652
|
startLoc?: AST.Position,
|
|
1653
1653
|
): ESTreeJSX.JSXOpeningElement;
|
|
1654
1654
|
// it could also be ESTreeJSX.JSXOpeningFragment
|
|
1655
|
-
// but not in our case since we don't use fragments
|
|
1656
1655
|
|
|
1657
1656
|
/**
|
|
1658
1657
|
* Parse JSX closing element at position
|
|
@@ -1706,7 +1705,7 @@ export namespace Parse {
|
|
|
1706
1705
|
}
|
|
1707
1706
|
|
|
1708
1707
|
/**
|
|
1709
|
-
* The constructor/class type for the extended
|
|
1708
|
+
* The constructor/class type for the extended TSRX parser.
|
|
1710
1709
|
* This represents the static side of the parser class after extending with plugins.
|
|
1711
1710
|
*/
|
|
1712
1711
|
export interface ParserConstructor {
|
|
@@ -1717,7 +1716,7 @@ export namespace Parse {
|
|
|
1717
1716
|
tokContexts: TokContexts;
|
|
1718
1717
|
/** TypeScript extensions when using acorn-typescript */
|
|
1719
1718
|
acornTypeScript: AcornTypeScriptExtensions;
|
|
1720
|
-
/** Static parse method that returns
|
|
1719
|
+
/** Static parse method that returns TSRX's extended Program type */
|
|
1721
1720
|
parse(input: string, options: Options): AST.Program;
|
|
1722
1721
|
/** Static parseExpressionAt method */
|
|
1723
1722
|
parseExpressionAt(input: string, pos: number, options: Options): AST.Expression;
|