ripple 0.3.9 → 0.3.11
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/CHANGELOG.md +43 -0
- package/package.json +2 -2
- package/src/compiler/errors.js +1 -1
- package/src/compiler/index.d.ts +3 -1
- package/src/compiler/phases/1-parse/index.js +195 -23
- package/src/compiler/phases/2-analyze/index.js +266 -108
- package/src/compiler/phases/2-analyze/prune.js +13 -5
- package/src/compiler/phases/3-transform/client/index.js +304 -80
- package/src/compiler/phases/3-transform/server/index.js +108 -43
- package/src/compiler/types/index.d.ts +28 -3
- package/src/compiler/types/parse.d.ts +3 -1
- package/src/compiler/utils.js +275 -1
- package/src/runtime/element.js +39 -0
- package/src/runtime/index-client.js +14 -4
- package/src/runtime/internal/client/composite.js +10 -6
- package/src/runtime/internal/client/expression.js +280 -0
- package/src/runtime/internal/client/index.js +4 -0
- package/src/runtime/internal/client/portal.js +12 -6
- package/src/runtime/internal/server/index.js +26 -1
- package/src/utils/builders.js +30 -0
- package/tests/client/basic/__snapshots__/basic.rendering.test.ripple.snap +1 -0
- package/tests/client/basic/basic.components.test.ripple +85 -87
- package/tests/client/basic/basic.errors.test.ripple +4 -8
- package/tests/client/basic/basic.rendering.test.ripple +27 -10
- package/tests/client/capture-error.js +12 -0
- package/tests/client/compiler/compiler.basic.test.ripple +76 -6
- package/tests/client/composite/composite.props.test.ripple +1 -3
- package/tests/client/composite/composite.render.test.ripple +91 -13
- package/tests/client/css/global-additional-cases.test.ripple +3 -3
- package/tests/client/return.test.ripple +101 -0
- package/tests/client/svg.test.ripple +4 -4
- package/tests/client/tsx.test.ripple +486 -0
- package/tests/hydration/basic.test.js +23 -0
- package/tests/hydration/compiled/client/basic.js +111 -75
- package/tests/hydration/compiled/client/composite.js +81 -46
- package/tests/hydration/compiled/client/events.js +18 -63
- package/tests/hydration/compiled/client/for.js +90 -183
- package/tests/hydration/compiled/client/head.js +10 -25
- package/tests/hydration/compiled/client/hmr.js +10 -13
- package/tests/hydration/compiled/client/html.js +251 -380
- package/tests/hydration/compiled/client/if-children.js +35 -45
- package/tests/hydration/compiled/client/if.js +2 -2
- package/tests/hydration/compiled/client/mixed-control-flow.js +24 -72
- package/tests/hydration/compiled/client/nested-control-flow.js +115 -391
- package/tests/hydration/compiled/client/portal.js +8 -20
- package/tests/hydration/compiled/client/reactivity.js +14 -47
- package/tests/hydration/compiled/client/return.js +2 -5
- package/tests/hydration/compiled/client/try.js +4 -4
- package/tests/hydration/compiled/server/basic.js +64 -31
- package/tests/hydration/compiled/server/composite.js +62 -29
- package/tests/hydration/compiled/server/hmr.js +24 -37
- package/tests/hydration/compiled/server/html.js +472 -611
- package/tests/hydration/compiled/server/if-children.js +77 -103
- package/tests/hydration/compiled/server/portal.js +8 -8
- package/tests/hydration/components/basic.ripple +15 -5
- package/tests/hydration/components/composite.ripple +13 -1
- package/tests/hydration/components/hmr.ripple +1 -3
- package/tests/hydration/components/html.ripple +13 -35
- package/tests/hydration/components/if-children.ripple +4 -8
- package/tests/hydration/composite.test.js +11 -0
- package/tests/server/basic.attributes.test.ripple +50 -0
- package/tests/server/basic.components.test.ripple +22 -28
- package/tests/server/basic.test.ripple +12 -0
- package/tests/server/compiler.test.ripple +25 -8
- package/tests/server/composite.props.test.ripple +1 -3
- package/tests/server/style-identifier.test.ripple +2 -4
- package/tests/utils/compiler-compat-config.test.js +38 -0
- package/tests/utils/vite-plugin-config.test.js +113 -0
- package/tsconfig.typecheck.json +2 -1
- package/types/index.d.ts +8 -11
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
is_inside_component,
|
|
27
27
|
is_ripple_track_call,
|
|
28
28
|
is_void_element,
|
|
29
|
+
is_children_template_expression as is_children_template_expression_in_scope,
|
|
29
30
|
normalize_children,
|
|
30
31
|
is_binding_function,
|
|
31
32
|
is_inside_try_block,
|
|
@@ -40,6 +41,123 @@ import { validate_nesting } from './validation.js';
|
|
|
40
41
|
|
|
41
42
|
const valid_in_head = new Set(['title', 'base', 'link', 'meta', 'style', 'script', 'noscript']);
|
|
42
43
|
|
|
44
|
+
const mutating_method_names = new Set([
|
|
45
|
+
'add',
|
|
46
|
+
'append',
|
|
47
|
+
'clear',
|
|
48
|
+
'copyWithin',
|
|
49
|
+
'delete',
|
|
50
|
+
'fill',
|
|
51
|
+
'pop',
|
|
52
|
+
'push',
|
|
53
|
+
'reverse',
|
|
54
|
+
'set',
|
|
55
|
+
'shift',
|
|
56
|
+
'sort',
|
|
57
|
+
'splice',
|
|
58
|
+
'unshift',
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {AST.MemberExpression} node
|
|
63
|
+
* @returns {string | null}
|
|
64
|
+
*/
|
|
65
|
+
function get_member_name(node) {
|
|
66
|
+
if (!node.computed && node.property.type === 'Identifier') {
|
|
67
|
+
return node.property.name;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (node.computed && node.property.type === 'Literal') {
|
|
71
|
+
return typeof node.property.value === 'string' ? node.property.value : null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {AST.CallExpression} node
|
|
79
|
+
* @returns {boolean}
|
|
80
|
+
*/
|
|
81
|
+
function is_mutating_call_expression(node) {
|
|
82
|
+
return (
|
|
83
|
+
node.callee.type === 'MemberExpression' &&
|
|
84
|
+
mutating_method_names.has(get_member_name(node.callee) ?? '')
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Check if an expression contains side effects or other impure operations.
|
|
90
|
+
* Template expressions should be pure reads.
|
|
91
|
+
* @param {AST.Expression | AST.SpreadElement | AST.Super | AST.Pattern} node
|
|
92
|
+
* @returns {boolean}
|
|
93
|
+
*/
|
|
94
|
+
function expression_has_side_effects(node) {
|
|
95
|
+
switch (node.type) {
|
|
96
|
+
case 'AssignmentExpression':
|
|
97
|
+
case 'UpdateExpression':
|
|
98
|
+
return true;
|
|
99
|
+
case 'SequenceExpression':
|
|
100
|
+
return node.expressions.some(expression_has_side_effects);
|
|
101
|
+
case 'ConditionalExpression':
|
|
102
|
+
return (
|
|
103
|
+
expression_has_side_effects(node.test) ||
|
|
104
|
+
expression_has_side_effects(node.consequent) ||
|
|
105
|
+
expression_has_side_effects(node.alternate)
|
|
106
|
+
);
|
|
107
|
+
case 'LogicalExpression':
|
|
108
|
+
case 'BinaryExpression':
|
|
109
|
+
return (
|
|
110
|
+
expression_has_side_effects(/** @type {AST.Expression} */ (node.left)) ||
|
|
111
|
+
expression_has_side_effects(node.right)
|
|
112
|
+
);
|
|
113
|
+
case 'UnaryExpression':
|
|
114
|
+
// delete operator has side effects (removes object properties)
|
|
115
|
+
if (node.operator === 'delete') return true;
|
|
116
|
+
return expression_has_side_effects(node.argument);
|
|
117
|
+
case 'AwaitExpression':
|
|
118
|
+
return expression_has_side_effects(node.argument);
|
|
119
|
+
case 'ChainExpression':
|
|
120
|
+
return expression_has_side_effects(node.expression);
|
|
121
|
+
case 'MemberExpression':
|
|
122
|
+
return (
|
|
123
|
+
expression_has_side_effects(node.object) ||
|
|
124
|
+
(node.computed &&
|
|
125
|
+
expression_has_side_effects(/** @type {AST.Expression} */ (node.property)))
|
|
126
|
+
);
|
|
127
|
+
case 'CallExpression':
|
|
128
|
+
return (
|
|
129
|
+
is_mutating_call_expression(node) ||
|
|
130
|
+
expression_has_side_effects(node.callee) ||
|
|
131
|
+
node.arguments.some(expression_has_side_effects)
|
|
132
|
+
);
|
|
133
|
+
case 'NewExpression':
|
|
134
|
+
return (
|
|
135
|
+
expression_has_side_effects(node.callee) || node.arguments.some(expression_has_side_effects)
|
|
136
|
+
);
|
|
137
|
+
case 'TemplateLiteral':
|
|
138
|
+
return node.expressions.some(expression_has_side_effects);
|
|
139
|
+
case 'TaggedTemplateExpression':
|
|
140
|
+
return (
|
|
141
|
+
expression_has_side_effects(node.tag) ||
|
|
142
|
+
node.quasi.expressions.some(expression_has_side_effects)
|
|
143
|
+
);
|
|
144
|
+
case 'ArrayExpression':
|
|
145
|
+
return node.elements.some((el) => el !== null && expression_has_side_effects(el));
|
|
146
|
+
case 'ObjectExpression':
|
|
147
|
+
return node.properties.some((prop) =>
|
|
148
|
+
prop.type === 'SpreadElement'
|
|
149
|
+
? expression_has_side_effects(prop.argument)
|
|
150
|
+
: expression_has_side_effects(prop.value) ||
|
|
151
|
+
(prop.computed &&
|
|
152
|
+
expression_has_side_effects(/** @type {AST.Expression} */ (prop.key))),
|
|
153
|
+
);
|
|
154
|
+
case 'SpreadElement':
|
|
155
|
+
return expression_has_side_effects(node.argument);
|
|
156
|
+
default:
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
43
161
|
/**
|
|
44
162
|
* @param {AnalysisContext['path']} path
|
|
45
163
|
*/
|
|
@@ -62,6 +180,7 @@ function mark_control_flow_has_template(path) {
|
|
|
62
180
|
node.type === 'TryStatement' ||
|
|
63
181
|
node.type === 'IfStatement' ||
|
|
64
182
|
node.type === 'SwitchStatement' ||
|
|
183
|
+
node.type === 'Tsx' ||
|
|
65
184
|
node.type === 'TsxCompat'
|
|
66
185
|
) {
|
|
67
186
|
node.metadata.has_template = true;
|
|
@@ -124,7 +243,11 @@ function setup_lazy_transforms(pattern, source_id, state, writable, is_track_cal
|
|
|
124
243
|
|
|
125
244
|
if (node.prefix) {
|
|
126
245
|
// ++count: return new value
|
|
127
|
-
return b.assignment(
|
|
246
|
+
return b.assignment(
|
|
247
|
+
'=',
|
|
248
|
+
/** @type {AST.Pattern} */ (member),
|
|
249
|
+
b.binary('+', fallback_read, delta),
|
|
250
|
+
);
|
|
128
251
|
} else {
|
|
129
252
|
// count++: return old value, write new value
|
|
130
253
|
// Use IIFE to declare temp variable
|
|
@@ -134,7 +257,13 @@ function setup_lazy_transforms(pattern, source_id, state, writable, is_track_cal
|
|
|
134
257
|
[],
|
|
135
258
|
b.block([
|
|
136
259
|
b.var(temp, fallback_read),
|
|
137
|
-
b.stmt(
|
|
260
|
+
b.stmt(
|
|
261
|
+
b.assignment(
|
|
262
|
+
'=',
|
|
263
|
+
/** @type {AST.Pattern} */ (member),
|
|
264
|
+
b.binary('+', temp, delta),
|
|
265
|
+
),
|
|
266
|
+
),
|
|
138
267
|
b.return(temp),
|
|
139
268
|
]),
|
|
140
269
|
),
|
|
@@ -197,7 +326,12 @@ function setup_lazy_array_transforms(pattern, source_id, state, writable) {
|
|
|
197
326
|
if (i === 0) {
|
|
198
327
|
// Fast path for index 0: use _$_.get(source) instead of source[0]
|
|
199
328
|
const read_expr = has_fallback
|
|
200
|
-
? () =>
|
|
329
|
+
? () =>
|
|
330
|
+
b.call(
|
|
331
|
+
'_$_.fallback',
|
|
332
|
+
b.call('_$_.get', source_id),
|
|
333
|
+
/** @type {AST.Expression} */ (fallback_value),
|
|
334
|
+
)
|
|
201
335
|
: () => b.call('_$_.get', source_id);
|
|
202
336
|
|
|
203
337
|
// Signal that read already produces an unwrapped value (calls _$_.get internally)
|
|
@@ -246,6 +380,7 @@ function setup_lazy_array_transforms(pattern, source_id, state, writable) {
|
|
|
246
380
|
} else {
|
|
247
381
|
binding.transform.update = (node) => {
|
|
248
382
|
const fn_name = node.prefix ? '_$_.update_pre' : '_$_.update';
|
|
383
|
+
/** @type {AST.Expression[]} */
|
|
249
384
|
const args = [source_id];
|
|
250
385
|
if (node.operator === '--') {
|
|
251
386
|
args.push(b.literal(-1));
|
|
@@ -279,7 +414,10 @@ function setup_lazy_array_transforms(pattern, source_id, state, writable) {
|
|
|
279
414
|
binding.kind = path.has_default_value ? 'lazy_fallback' : 'lazy';
|
|
280
415
|
|
|
281
416
|
binding.transform = {
|
|
282
|
-
read: (_) =>
|
|
417
|
+
read: (_) =>
|
|
418
|
+
path.expression(
|
|
419
|
+
/** @type {AST.Identifier | AST.CallExpression} */ (base_expression(source_id)),
|
|
420
|
+
),
|
|
283
421
|
};
|
|
284
422
|
|
|
285
423
|
if (writable) {
|
|
@@ -287,7 +425,7 @@ function setup_lazy_array_transforms(pattern, source_id, state, writable) {
|
|
|
287
425
|
return b.assignment(
|
|
288
426
|
'=',
|
|
289
427
|
/** @type {AST.MemberExpression} */ (
|
|
290
|
-
path.update_expression(base_expression(source_id))
|
|
428
|
+
path.update_expression(/** @type {AST.Identifier} */ (base_expression(source_id)))
|
|
291
429
|
),
|
|
292
430
|
value,
|
|
293
431
|
);
|
|
@@ -295,12 +433,20 @@ function setup_lazy_array_transforms(pattern, source_id, state, writable) {
|
|
|
295
433
|
|
|
296
434
|
if (path.has_default_value) {
|
|
297
435
|
binding.transform.update = (node) => {
|
|
298
|
-
const member = path.update_expression(
|
|
299
|
-
|
|
436
|
+
const member = path.update_expression(
|
|
437
|
+
/** @type {AST.Identifier} */ (base_expression(source_id)),
|
|
438
|
+
);
|
|
439
|
+
const fallback_read = path.expression(
|
|
440
|
+
/** @type {AST.Identifier | AST.CallExpression} */ (base_expression(source_id)),
|
|
441
|
+
);
|
|
300
442
|
const delta = node.operator === '++' ? b.literal(1) : b.literal(-1);
|
|
301
443
|
|
|
302
444
|
if (node.prefix) {
|
|
303
|
-
return b.assignment(
|
|
445
|
+
return b.assignment(
|
|
446
|
+
'=',
|
|
447
|
+
/** @type {AST.Pattern} */ (member),
|
|
448
|
+
b.binary('+', fallback_read, delta),
|
|
449
|
+
);
|
|
304
450
|
} else {
|
|
305
451
|
const temp = b.id('_v');
|
|
306
452
|
return b.call(
|
|
@@ -308,7 +454,13 @@ function setup_lazy_array_transforms(pattern, source_id, state, writable) {
|
|
|
308
454
|
[],
|
|
309
455
|
b.block([
|
|
310
456
|
b.var(temp, fallback_read),
|
|
311
|
-
b.stmt(
|
|
457
|
+
b.stmt(
|
|
458
|
+
b.assignment(
|
|
459
|
+
'=',
|
|
460
|
+
/** @type {AST.Pattern} */ (member),
|
|
461
|
+
b.binary('+', temp, delta),
|
|
462
|
+
),
|
|
463
|
+
),
|
|
312
464
|
b.return(temp),
|
|
313
465
|
]),
|
|
314
466
|
),
|
|
@@ -319,7 +471,7 @@ function setup_lazy_array_transforms(pattern, source_id, state, writable) {
|
|
|
319
471
|
binding.transform.update = (node) =>
|
|
320
472
|
b.update(
|
|
321
473
|
node.operator,
|
|
322
|
-
path.update_expression(base_expression(source_id)),
|
|
474
|
+
path.update_expression(/** @type {AST.Identifier} */ (base_expression(source_id))),
|
|
323
475
|
node.prefix,
|
|
324
476
|
);
|
|
325
477
|
}
|
|
@@ -347,11 +499,11 @@ function unwrap_type_annotation(type_annotation) {
|
|
|
347
499
|
|
|
348
500
|
while (annotation) {
|
|
349
501
|
if (annotation.type === 'TSParenthesizedType') {
|
|
350
|
-
annotation = annotation.typeAnnotation;
|
|
502
|
+
annotation = /** @type {AST.TypeNode | undefined} */ (annotation.typeAnnotation);
|
|
351
503
|
continue;
|
|
352
504
|
}
|
|
353
505
|
if (annotation.type === 'TSOptionalType') {
|
|
354
|
-
annotation = annotation.typeAnnotation;
|
|
506
|
+
annotation = /** @type {AST.TypeNode | undefined} */ (annotation.typeAnnotation);
|
|
355
507
|
continue;
|
|
356
508
|
}
|
|
357
509
|
break;
|
|
@@ -374,11 +526,11 @@ function normalize_tuple_element_type(type_annotation) {
|
|
|
374
526
|
continue;
|
|
375
527
|
}
|
|
376
528
|
if (annotation.type === 'TSParenthesizedType') {
|
|
377
|
-
annotation = annotation.typeAnnotation;
|
|
529
|
+
annotation = /** @type {AST.TypeNode} */ (annotation.typeAnnotation);
|
|
378
530
|
continue;
|
|
379
531
|
}
|
|
380
532
|
if (annotation.type === 'TSOptionalType') {
|
|
381
|
-
annotation = annotation.typeAnnotation;
|
|
533
|
+
annotation = /** @type {AST.TypeNode} */ (annotation.typeAnnotation);
|
|
382
534
|
continue;
|
|
383
535
|
}
|
|
384
536
|
break;
|
|
@@ -430,7 +582,7 @@ function get_object_property_type_annotation(type_annotation, property) {
|
|
|
430
582
|
return undefined;
|
|
431
583
|
}
|
|
432
584
|
|
|
433
|
-
const key_name = get_object_pattern_key_name(property.key);
|
|
585
|
+
const key_name = get_object_pattern_key_name(/** @type {AST.Expression} */ (property.key));
|
|
434
586
|
if (key_name === null) {
|
|
435
587
|
return undefined;
|
|
436
588
|
}
|
|
@@ -687,37 +839,6 @@ function error_return_keyword(node, context, message) {
|
|
|
687
839
|
);
|
|
688
840
|
}
|
|
689
841
|
|
|
690
|
-
/**
|
|
691
|
-
* @param {AST.Expression} expression
|
|
692
|
-
* @returns {AST.Expression}
|
|
693
|
-
*/
|
|
694
|
-
function unwrap_template_expression(expression) {
|
|
695
|
-
/** @type {AST.Expression} */
|
|
696
|
-
let node = expression;
|
|
697
|
-
|
|
698
|
-
while (true) {
|
|
699
|
-
if (
|
|
700
|
-
node.type === 'ParenthesizedExpression' ||
|
|
701
|
-
node.type === 'TSAsExpression' ||
|
|
702
|
-
node.type === 'TSSatisfiesExpression' ||
|
|
703
|
-
node.type === 'TSNonNullExpression' ||
|
|
704
|
-
node.type === 'TSInstantiationExpression'
|
|
705
|
-
) {
|
|
706
|
-
node = /** @type {AST.Expression} */ (node.expression);
|
|
707
|
-
continue;
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
if (node.type === 'ChainExpression') {
|
|
711
|
-
node = /** @type {AST.Expression} */ (node.expression);
|
|
712
|
-
continue;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
break;
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
return node;
|
|
719
|
-
}
|
|
720
|
-
|
|
721
842
|
/**
|
|
722
843
|
* @param {AST.Expression} expression
|
|
723
844
|
* @param {Context<AST.Node, AnalysisState>} context
|
|
@@ -726,44 +847,7 @@ function unwrap_template_expression(expression) {
|
|
|
726
847
|
function is_children_template_expression(expression, context) {
|
|
727
848
|
const component = context.path.findLast((node) => node.type === 'Component');
|
|
728
849
|
const component_scope = component ? context.state.scopes.get(component) : null;
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
if (unwrapped.type === 'MemberExpression') {
|
|
732
|
-
let property_name = null;
|
|
733
|
-
|
|
734
|
-
if (!unwrapped.computed && unwrapped.property.type === 'Identifier') {
|
|
735
|
-
property_name = unwrapped.property.name;
|
|
736
|
-
} else if (
|
|
737
|
-
unwrapped.computed &&
|
|
738
|
-
unwrapped.property.type === 'Literal' &&
|
|
739
|
-
typeof unwrapped.property.value === 'string'
|
|
740
|
-
) {
|
|
741
|
-
property_name = unwrapped.property.value;
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
if (property_name === 'children') {
|
|
745
|
-
const target = unwrap_template_expression(/** @type {AST.Expression} */ (unwrapped.object));
|
|
746
|
-
|
|
747
|
-
if (target.type === 'Identifier') {
|
|
748
|
-
const binding = context.state.scope.get(target.name);
|
|
749
|
-
return binding?.declaration_kind === 'param' && binding.scope === component_scope;
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
if (unwrapped.type !== 'Identifier' || unwrapped.name !== 'children') {
|
|
755
|
-
return false;
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
const binding = context.state.scope.get(unwrapped.name);
|
|
759
|
-
return (
|
|
760
|
-
(binding?.declaration_kind === 'param' ||
|
|
761
|
-
binding?.kind === 'prop' ||
|
|
762
|
-
binding?.kind === 'prop_fallback' ||
|
|
763
|
-
binding?.kind === 'lazy' ||
|
|
764
|
-
binding?.kind === 'lazy_fallback') &&
|
|
765
|
-
binding.scope === component_scope
|
|
766
|
-
);
|
|
850
|
+
return is_children_template_expression_in_scope(expression, context.state.scope, component_scope);
|
|
767
851
|
}
|
|
768
852
|
|
|
769
853
|
/** @type {Visitors<AST.Node, AnalysisState>} */
|
|
@@ -995,11 +1079,13 @@ const visitors = {
|
|
|
995
1079
|
const callee = node.callee;
|
|
996
1080
|
|
|
997
1081
|
if (
|
|
998
|
-
!context.path.some(
|
|
1082
|
+
!context.path.some(
|
|
1083
|
+
(path_node) => path_node.type === 'TsxCompat' || path_node.type === 'Tsx',
|
|
1084
|
+
) &&
|
|
999
1085
|
is_children_template_expression(/** @type {AST.Expression} */ (callee), context)
|
|
1000
1086
|
) {
|
|
1001
1087
|
error(
|
|
1002
|
-
'`children` cannot be called like a regular function.
|
|
1088
|
+
'`children` cannot be called like a regular function. Render it with `{children}` or `{props.children}` instead.',
|
|
1003
1089
|
context.state.analysis.module.filename,
|
|
1004
1090
|
callee,
|
|
1005
1091
|
context.state.loose ? context.state.analysis.errors : undefined,
|
|
@@ -1482,6 +1568,7 @@ const visitors = {
|
|
|
1482
1568
|
...node.metadata,
|
|
1483
1569
|
has_template: false,
|
|
1484
1570
|
has_await: false,
|
|
1571
|
+
has_throw: false,
|
|
1485
1572
|
};
|
|
1486
1573
|
|
|
1487
1574
|
const test_metadata = { tracking: false };
|
|
@@ -1503,7 +1590,7 @@ const visitors = {
|
|
|
1503
1590
|
node.metadata.lone_return = true;
|
|
1504
1591
|
}
|
|
1505
1592
|
|
|
1506
|
-
if (!node.metadata.has_template && !node.metadata.has_return) {
|
|
1593
|
+
if (!node.metadata.has_template && !node.metadata.has_return && !node.metadata.has_throw) {
|
|
1507
1594
|
error(
|
|
1508
1595
|
'Component if statements must contain a template in their "then" body. Move the if statement into an effect if it does not render anything.',
|
|
1509
1596
|
context.state.analysis.module.filename,
|
|
@@ -1518,9 +1605,10 @@ const visitors = {
|
|
|
1518
1605
|
const saved_returns = node.metadata.returns;
|
|
1519
1606
|
node.metadata.has_template = false;
|
|
1520
1607
|
node.metadata.has_await = false;
|
|
1608
|
+
node.metadata.has_throw = false;
|
|
1521
1609
|
context.visit(node.alternate, context.state);
|
|
1522
1610
|
|
|
1523
|
-
if (!node.metadata.has_template && !node.metadata.has_return) {
|
|
1611
|
+
if (!node.metadata.has_template && !node.metadata.has_return && !node.metadata.has_throw) {
|
|
1524
1612
|
error(
|
|
1525
1613
|
'Component if statements must contain a template in their "else" body. Move the if statement into an effect if it does not render anything.',
|
|
1526
1614
|
context.state.analysis.module.filename,
|
|
@@ -1589,6 +1677,33 @@ const visitors = {
|
|
|
1589
1677
|
}
|
|
1590
1678
|
},
|
|
1591
1679
|
|
|
1680
|
+
ThrowStatement(node, context) {
|
|
1681
|
+
if (!is_inside_component(context)) {
|
|
1682
|
+
return context.next();
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
for (let i = context.path.length - 1; i >= 0; i--) {
|
|
1686
|
+
const ancestor = context.path[i];
|
|
1687
|
+
|
|
1688
|
+
if (
|
|
1689
|
+
ancestor.type === 'Component' ||
|
|
1690
|
+
ancestor.type === 'FunctionExpression' ||
|
|
1691
|
+
ancestor.type === 'ArrowFunctionExpression' ||
|
|
1692
|
+
ancestor.type === 'FunctionDeclaration'
|
|
1693
|
+
) {
|
|
1694
|
+
break;
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
if (ancestor.type === 'IfStatement') {
|
|
1698
|
+
if (!ancestor.metadata.has_throw) {
|
|
1699
|
+
ancestor.metadata.has_throw = true;
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
context.next();
|
|
1705
|
+
},
|
|
1706
|
+
|
|
1592
1707
|
TryStatement(node, context) {
|
|
1593
1708
|
const { state } = context;
|
|
1594
1709
|
if (!is_inside_component(context)) {
|
|
@@ -1684,7 +1799,7 @@ const visitors = {
|
|
|
1684
1799
|
},
|
|
1685
1800
|
|
|
1686
1801
|
JSXElement(node, context) {
|
|
1687
|
-
const inside_tsx_compat = context.path.some((n) => n.type === 'TsxCompat');
|
|
1802
|
+
const inside_tsx_compat = context.path.some((n) => n.type === 'TsxCompat' || n.type === 'Tsx');
|
|
1688
1803
|
|
|
1689
1804
|
if (inside_tsx_compat) {
|
|
1690
1805
|
return context.next();
|
|
@@ -1697,8 +1812,25 @@ const visitors = {
|
|
|
1697
1812
|
);
|
|
1698
1813
|
},
|
|
1699
1814
|
|
|
1700
|
-
|
|
1815
|
+
Tsx(_, context) {
|
|
1816
|
+
mark_control_flow_has_template(context.path);
|
|
1817
|
+
return context.next();
|
|
1818
|
+
},
|
|
1819
|
+
|
|
1820
|
+
TsxCompat(node, context) {
|
|
1701
1821
|
mark_control_flow_has_template(context.path);
|
|
1822
|
+
|
|
1823
|
+
const configured_compat_kinds = context.state.configured_compat_kinds;
|
|
1824
|
+
if (configured_compat_kinds !== undefined && !configured_compat_kinds.has(node.kind)) {
|
|
1825
|
+
error(
|
|
1826
|
+
`<tsx:${node.kind}> requires "${node.kind}" compat to be configured in ripple.config.ts.`,
|
|
1827
|
+
context.state.analysis.module.filename,
|
|
1828
|
+
node,
|
|
1829
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
1830
|
+
context.state.analysis.comments,
|
|
1831
|
+
);
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1702
1834
|
return context.next();
|
|
1703
1835
|
},
|
|
1704
1836
|
|
|
@@ -1718,6 +1850,19 @@ const visitors = {
|
|
|
1718
1850
|
|
|
1719
1851
|
mark_control_flow_has_template(path);
|
|
1720
1852
|
|
|
1853
|
+
if (
|
|
1854
|
+
!is_dom_element &&
|
|
1855
|
+
is_children_template_expression(/** @type {AST.Expression} */ (node.id), context)
|
|
1856
|
+
) {
|
|
1857
|
+
error(
|
|
1858
|
+
'`children` cannot be rendered as a component. Render it with `{children}` or `{props.children}` instead.',
|
|
1859
|
+
state.analysis.module.filename,
|
|
1860
|
+
node.id,
|
|
1861
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
1862
|
+
context.state.analysis.comments,
|
|
1863
|
+
);
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1721
1866
|
validate_nesting(node, context);
|
|
1722
1867
|
|
|
1723
1868
|
// Store capitalized name for dynamic components/elements
|
|
@@ -1771,7 +1916,10 @@ const visitors = {
|
|
|
1771
1916
|
if (/** @type {AST.Identifier} */ (node.id).name === 'title') {
|
|
1772
1917
|
const children = normalize_children(node.children, context);
|
|
1773
1918
|
|
|
1774
|
-
if (
|
|
1919
|
+
if (
|
|
1920
|
+
children.length !== 1 ||
|
|
1921
|
+
(children[0].type !== 'RippleExpression' && children[0].type !== 'Text')
|
|
1922
|
+
) {
|
|
1775
1923
|
// TODO: could transform children as something, e.g. Text Node, and avoid a fatal error
|
|
1776
1924
|
error(
|
|
1777
1925
|
'<title> must have only contain text nodes',
|
|
@@ -1916,30 +2064,22 @@ const visitors = {
|
|
|
1916
2064
|
}
|
|
1917
2065
|
/** @type {(AST.Node | AST.Expression)[]} */
|
|
1918
2066
|
let implicit_children = [];
|
|
1919
|
-
/** @type {AST.Identifier[]} */
|
|
1920
|
-
let explicit_children = [];
|
|
1921
2067
|
|
|
1922
2068
|
for (const child of node.children) {
|
|
1923
2069
|
if (child.type === 'Component') {
|
|
1924
|
-
if (child.id?.name === 'children') {
|
|
1925
|
-
explicit_children.push(child.id);
|
|
1926
|
-
}
|
|
1927
|
-
} else if (child.type !== 'EmptyStatement') {
|
|
1928
|
-
implicit_children.push(
|
|
1929
|
-
child.type === 'Text' || child.type === 'Html' ? child.expression : child,
|
|
1930
|
-
);
|
|
1931
|
-
}
|
|
1932
|
-
}
|
|
1933
|
-
|
|
1934
|
-
if (implicit_children.length > 0 && explicit_children.length > 0) {
|
|
1935
|
-
for (const item of [...explicit_children, ...implicit_children]) {
|
|
1936
2070
|
error(
|
|
1937
|
-
'
|
|
2071
|
+
'Component declarations cannot be used inside composite component children. Pass them as explicit props on the template element instead.',
|
|
1938
2072
|
state.analysis.module.filename,
|
|
1939
|
-
|
|
2073
|
+
child.id || child,
|
|
1940
2074
|
context.state.loose ? context.state.analysis.errors : undefined,
|
|
1941
2075
|
context.state.analysis.comments,
|
|
1942
2076
|
);
|
|
2077
|
+
} else if (child.type !== 'EmptyStatement') {
|
|
2078
|
+
implicit_children.push(
|
|
2079
|
+
child.type === 'RippleExpression' || child.type === 'Text' || child.type === 'Html'
|
|
2080
|
+
? child.expression
|
|
2081
|
+
: child,
|
|
2082
|
+
);
|
|
1943
2083
|
}
|
|
1944
2084
|
}
|
|
1945
2085
|
}
|
|
@@ -1966,12 +2106,28 @@ const visitors = {
|
|
|
1966
2106
|
};
|
|
1967
2107
|
},
|
|
1968
2108
|
|
|
2109
|
+
RippleExpression(node, context) {
|
|
2110
|
+
mark_control_flow_has_template(context.path);
|
|
2111
|
+
|
|
2112
|
+
if (expression_has_side_effects(node.expression)) {
|
|
2113
|
+
error(
|
|
2114
|
+
'Template expressions must not contain side effects.',
|
|
2115
|
+
context.state.analysis.module.filename,
|
|
2116
|
+
node.expression,
|
|
2117
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
2118
|
+
context.state.analysis.comments,
|
|
2119
|
+
);
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
context.next();
|
|
2123
|
+
},
|
|
2124
|
+
|
|
1969
2125
|
Text(node, context) {
|
|
1970
2126
|
mark_control_flow_has_template(context.path);
|
|
1971
2127
|
|
|
1972
2128
|
if (is_children_template_expression(/** @type {AST.Expression} */ (node.expression), context)) {
|
|
1973
2129
|
error(
|
|
1974
|
-
'`children` cannot be rendered using text interpolation. Use
|
|
2130
|
+
'`children` cannot be rendered using explicit text interpolation. Use `{children}` or `{props.children}` instead.',
|
|
1975
2131
|
context.state.analysis.module.filename,
|
|
1976
2132
|
node.expression,
|
|
1977
2133
|
context.state.loose ? context.state.analysis.errors : undefined,
|
|
@@ -2070,6 +2226,8 @@ export function analyze(ast, filename, options = {}) {
|
|
|
2070
2226
|
ancestor_server_block: undefined,
|
|
2071
2227
|
to_ts: options.to_ts ?? false,
|
|
2072
2228
|
loose,
|
|
2229
|
+
configured_compat_kinds:
|
|
2230
|
+
options.compat_kinds === undefined ? undefined : new Set(options.compat_kinds),
|
|
2073
2231
|
metadata: {},
|
|
2074
2232
|
mode: options.mode,
|
|
2075
2233
|
},
|
|
@@ -306,12 +306,20 @@ function get_descendant_elements(node, adjacent_only) {
|
|
|
306
306
|
}
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
-
// For template nodes and
|
|
309
|
+
// For template nodes and interpolation expressions
|
|
310
310
|
if (
|
|
311
|
-
|
|
312
|
-
|
|
311
|
+
(current_node.type === 'RippleExpression' ||
|
|
312
|
+
current_node.type === 'Text' ||
|
|
313
|
+
current_node.type === 'Html') &&
|
|
314
|
+
/** @type {AST.RippleExpression | AST.Html | AST.TextNode} */ (current_node).expression &&
|
|
315
|
+
typeof (
|
|
316
|
+
/** @type {AST.RippleExpression | AST.Html | AST.TextNode} */ (current_node).expression
|
|
317
|
+
) === 'object'
|
|
313
318
|
) {
|
|
314
|
-
visit(
|
|
319
|
+
visit(
|
|
320
|
+
/** @type {AST.RippleExpression | AST.Html | AST.TextNode} */ (current_node).expression,
|
|
321
|
+
depth + 1,
|
|
322
|
+
);
|
|
315
323
|
}
|
|
316
324
|
}
|
|
317
325
|
|
|
@@ -409,7 +417,7 @@ function get_possible_element_siblings(node, direction, adjacent_only) {
|
|
|
409
417
|
// Stop at non-whitespace text nodes for adjacent selectors
|
|
410
418
|
else if (
|
|
411
419
|
adjacent_only &&
|
|
412
|
-
sibling.type === 'Text' &&
|
|
420
|
+
(sibling.type === 'RippleExpression' || sibling.type === 'Text') &&
|
|
413
421
|
sibling.expression.type === 'Literal' &&
|
|
414
422
|
typeof sibling.expression.value === 'string' &&
|
|
415
423
|
sibling.expression.value.trim()
|