ripple 0.3.3 → 0.3.5
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 +90 -0
- package/package.json +2 -2
- package/src/compiler/identifier-utils.js +1 -8
- package/src/compiler/phases/1-parse/index.js +101 -195
- package/src/compiler/phases/2-analyze/index.js +115 -174
- package/src/compiler/phases/2-analyze/prune.js +2 -2
- package/src/compiler/phases/3-transform/client/index.js +177 -261
- package/src/compiler/phases/3-transform/server/index.js +185 -42
- package/src/compiler/types/index.d.ts +15 -34
- package/src/compiler/utils.js +32 -20
- package/src/runtime/index-client.js +0 -17
- package/src/runtime/internal/client/bindings.js +118 -7
- package/src/runtime/internal/client/render.js +5 -1
- package/src/runtime/internal/client/runtime.js +1 -1
- package/src/runtime/internal/client/types.d.ts +4 -0
- package/src/runtime/internal/server/index.js +11 -0
- package/tests/client/array/array.copy-within.test.ripple +7 -7
- package/tests/client/array/array.derived.test.ripple +24 -24
- package/tests/client/array/array.iteration.test.ripple +7 -7
- package/tests/client/array/array.mutations.test.ripple +17 -17
- package/tests/client/array/array.to-methods.test.ripple +4 -4
- package/tests/client/async-suspend.test.ripple +3 -3
- package/tests/client/basic/basic.attributes.test.ripple +31 -31
- package/tests/client/basic/basic.collections.test.ripple +6 -6
- package/tests/client/basic/basic.components.test.ripple +8 -8
- package/tests/client/basic/basic.errors.test.ripple +31 -34
- package/tests/client/basic/basic.events.test.ripple +11 -11
- package/tests/client/basic/basic.get-set.test.ripple +18 -18
- package/tests/client/basic/basic.reactivity.test.ripple +36 -36
- package/tests/client/basic/basic.rendering.test.ripple +7 -7
- package/tests/client/basic/basic.utilities.test.ripple +4 -4
- package/tests/client/boundaries.test.ripple +7 -7
- package/tests/client/compiler/__snapshots__/compiler.typescript.test.ripple.snap +24 -0
- package/tests/client/compiler/compiler.assignments.test.ripple +12 -10
- package/tests/client/compiler/compiler.basic.test.ripple +57 -58
- package/tests/client/compiler/compiler.tracked-access.test.ripple +14 -8
- package/tests/client/compiler/compiler.typescript.test.ripple +31 -0
- package/tests/client/composite/composite.dynamic-components.test.ripple +6 -6
- package/tests/client/composite/composite.props.test.ripple +9 -9
- package/tests/client/composite/composite.reactivity.test.ripple +23 -23
- package/tests/client/composite/composite.render.test.ripple +52 -4
- package/tests/client/computed-properties.test.ripple +3 -3
- package/tests/client/context.test.ripple +3 -3
- package/tests/client/css/global-additional-cases.test.ripple +5 -2
- package/tests/client/css/style-identifier.test.ripple +40 -49
- package/tests/client/date.test.ripple +39 -39
- package/tests/client/dynamic-elements.test.ripple +37 -37
- package/tests/client/events.test.ripple +25 -25
- package/tests/client/for.test.ripple +8 -8
- package/tests/client/head.test.ripple +7 -7
- package/tests/client/html.test.ripple +2 -2
- package/tests/client/input-value.test.ripple +376 -177
- package/tests/client/lazy-destructuring.test.ripple +209 -0
- package/tests/client/map.test.ripple +20 -20
- package/tests/client/media-query.test.ripple +4 -4
- package/tests/client/object.test.ripple +5 -5
- package/tests/client/portal.test.ripple +4 -4
- package/tests/client/ref.test.ripple +3 -3
- package/tests/client/return.test.ripple +17 -17
- package/tests/client/set.test.ripple +10 -10
- package/tests/client/svg.test.ripple +6 -5
- package/tests/client/switch.test.ripple +10 -10
- package/tests/client/tracked-expression.test.ripple +3 -1
- package/tests/client/try.test.ripple +4 -4
- package/tests/client/url/url.derived.test.ripple +6 -7
- package/tests/client/url/url.parsing.test.ripple +9 -9
- package/tests/client/url/url.partial-removal.test.ripple +9 -9
- package/tests/client/url/url.reactivity.test.ripple +16 -16
- package/tests/client/url/url.serialization.test.ripple +3 -3
- package/tests/client/url-search-params/url-search-params.derived.test.ripple +7 -8
- package/tests/client/url-search-params/url-search-params.initialization.test.ripple +6 -4
- package/tests/client/url-search-params/url-search-params.iteration.test.ripple +12 -12
- package/tests/client/url-search-params/url-search-params.mutation.test.ripple +18 -18
- package/tests/client/url-search-params/url-search-params.retrieval.test.ripple +16 -16
- package/tests/client/url-search-params/url-search-params.serialization.test.ripple +4 -4
- package/tests/client/url-search-params/url-search-params.tracked-url.test.ripple +3 -3
- package/tests/hydration/build-components.js +4 -10
- package/tests/hydration/compiled/client/basic.js +4 -4
- package/tests/hydration/compiled/client/events.js +2 -0
- package/tests/hydration/compiled/client/for.js +2 -0
- package/tests/hydration/compiled/client/head.js +13 -11
- package/tests/hydration/compiled/client/hmr.js +4 -2
- package/tests/hydration/compiled/client/html.js +82 -95
- package/tests/hydration/compiled/client/if-children.js +8 -9
- package/tests/hydration/compiled/client/if.js +2 -0
- package/tests/hydration/compiled/client/mixed-control-flow.js +4 -2
- package/tests/hydration/compiled/client/portal.js +1 -1
- package/tests/hydration/compiled/client/reactivity.js +2 -0
- package/tests/hydration/compiled/client/return.js +2 -0
- package/tests/hydration/compiled/client/switch.js +2 -0
- package/tests/hydration/compiled/server/composite.js +2 -2
- package/tests/hydration/compiled/server/events.js +2 -0
- package/tests/hydration/compiled/server/for.js +2 -0
- package/tests/hydration/compiled/server/head.js +13 -11
- package/tests/hydration/compiled/server/hmr.js +2 -0
- package/tests/hydration/compiled/server/html.js +2 -0
- package/tests/hydration/compiled/server/if-children.js +2 -0
- package/tests/hydration/compiled/server/if.js +2 -0
- package/tests/hydration/compiled/server/mixed-control-flow.js +2 -0
- package/tests/hydration/compiled/server/portal.js +1 -1
- package/tests/hydration/compiled/server/reactivity.js +2 -0
- package/tests/hydration/compiled/server/return.js +2 -0
- package/tests/hydration/compiled/server/switch.js +2 -0
- package/tests/hydration/components/composite.ripple +1 -1
- package/tests/hydration/components/events.ripple +10 -8
- package/tests/hydration/components/for.ripple +22 -20
- package/tests/hydration/components/head.ripple +8 -6
- package/tests/hydration/components/hmr.ripple +3 -1
- package/tests/hydration/components/html.ripple +3 -1
- package/tests/hydration/components/if-children.ripple +9 -7
- package/tests/hydration/components/if.ripple +7 -5
- package/tests/hydration/components/mixed-control-flow.ripple +5 -3
- package/tests/hydration/components/portal.ripple +2 -2
- package/tests/hydration/components/reactivity.ripple +11 -9
- package/tests/hydration/components/return.ripple +13 -11
- package/tests/hydration/components/switch.ripple +6 -4
- package/tests/server/__snapshots__/compiler.test.ripple.snap +22 -0
- package/tests/server/await.test.ripple +2 -2
- package/tests/server/basic.attributes.test.ripple +21 -19
- package/tests/server/basic.components.test.ripple +5 -4
- package/tests/server/basic.test.ripple +21 -20
- package/tests/server/compiler.test.ripple +36 -5
- package/tests/server/composite.props.test.ripple +7 -6
- package/tests/server/context.test.ripple +3 -1
- package/tests/server/dynamic-elements.test.ripple +24 -24
- package/tests/server/head.test.ripple +7 -5
- package/tests/server/lazy-destructuring.test.ripple +103 -0
- package/tests/server/style-identifier.test.ripple +95 -16
|
@@ -68,6 +68,79 @@ function mark_control_flow_has_template(path) {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Set up lazy destructuring transforms for bindings extracted from a lazy pattern.
|
|
73
|
+
* Converts each destructured identifier into a binding that lazily accesses properties
|
|
74
|
+
* on the source identifier (e.g., `a` → `source.a` for object, `a` → `source[0]` for array).
|
|
75
|
+
* @param {AST.ObjectPattern | AST.ArrayPattern} pattern - The destructuring pattern with lazy: true
|
|
76
|
+
* @param {AST.Identifier} source_id - The identifier to access properties on
|
|
77
|
+
* @param {AnalysisState} state - The analysis state
|
|
78
|
+
* @param {boolean} writable - Whether assignments/updates should be supported (let vs const)
|
|
79
|
+
*/
|
|
80
|
+
function setup_lazy_transforms(pattern, source_id, state, writable) {
|
|
81
|
+
const paths = extract_paths(pattern);
|
|
82
|
+
|
|
83
|
+
for (const path of paths) {
|
|
84
|
+
const name = /** @type {AST.Identifier} */ (path.node).name;
|
|
85
|
+
const binding = state.scope.get(name);
|
|
86
|
+
|
|
87
|
+
if (binding !== null) {
|
|
88
|
+
const has_fallback = path.has_default_value;
|
|
89
|
+
binding.kind = has_fallback ? 'lazy_fallback' : 'lazy';
|
|
90
|
+
|
|
91
|
+
binding.transform = {
|
|
92
|
+
read: (_) => {
|
|
93
|
+
return path.expression(source_id);
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if (writable) {
|
|
98
|
+
binding.transform.assign = (node, value) => {
|
|
99
|
+
return b.assignment(
|
|
100
|
+
'=',
|
|
101
|
+
/** @type {AST.MemberExpression} */ (path.update_expression(source_id)),
|
|
102
|
+
value,
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
if (has_fallback) {
|
|
107
|
+
// For bindings with default values, generate proper fallback-aware update
|
|
108
|
+
// e.g., count++ with default 0 becomes:
|
|
109
|
+
// (() => { var _v = _$_.fallback(obj.count, 0); obj.count = _v + 1; return _v; })() for postfix
|
|
110
|
+
// (obj.count = _$_.fallback(obj.count, 0) + 1) for prefix
|
|
111
|
+
binding.transform.update = (node) => {
|
|
112
|
+
const member = path.update_expression(source_id);
|
|
113
|
+
const fallback_read = path.expression(source_id);
|
|
114
|
+
const delta = node.operator === '++' ? b.literal(1) : b.literal(-1);
|
|
115
|
+
|
|
116
|
+
if (node.prefix) {
|
|
117
|
+
// ++count: return new value
|
|
118
|
+
return b.assignment('=', member, b.binary('+', fallback_read, delta));
|
|
119
|
+
} else {
|
|
120
|
+
// count++: return old value, write new value
|
|
121
|
+
// Use IIFE to declare temp variable
|
|
122
|
+
const temp = b.id('_v');
|
|
123
|
+
return b.call(
|
|
124
|
+
b.arrow(
|
|
125
|
+
[],
|
|
126
|
+
b.block([
|
|
127
|
+
b.var(temp, fallback_read),
|
|
128
|
+
b.stmt(b.assignment('=', member, b.binary('+', temp, delta))),
|
|
129
|
+
b.return(temp),
|
|
130
|
+
]),
|
|
131
|
+
),
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
} else {
|
|
136
|
+
binding.transform.update = (node) =>
|
|
137
|
+
b.update(node.operator, path.update_expression(source_id), node.prefix);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
71
144
|
/**
|
|
72
145
|
* @param {AST.Function} node
|
|
73
146
|
* @param {AnalysisContext} context
|
|
@@ -78,6 +151,19 @@ function visit_function(node, context) {
|
|
|
78
151
|
path: [...context.path],
|
|
79
152
|
};
|
|
80
153
|
|
|
154
|
+
// Set up lazy transforms for any lazy destructured parameters
|
|
155
|
+
for (let i = 0; i < node.params.length; i++) {
|
|
156
|
+
const param_node = node.params[i];
|
|
157
|
+
const param = param_node.type === 'AssignmentPattern' ? param_node.left : param_node;
|
|
158
|
+
|
|
159
|
+
if ((param.type === 'ObjectPattern' || param.type === 'ArrayPattern') && param.lazy) {
|
|
160
|
+
const param_id = b.id(context.state.scope.generate('param'));
|
|
161
|
+
setup_lazy_transforms(param, param_id, context.state, true);
|
|
162
|
+
// Store the generated identifier name on the pattern for the transform phase
|
|
163
|
+
param.metadata = { ...param.metadata, lazy_id: param_id.name };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
81
167
|
context.next({
|
|
82
168
|
...context.state,
|
|
83
169
|
function_depth: (context.state.function_depth ?? 0) + 1,
|
|
@@ -239,7 +325,7 @@ const visitors = {
|
|
|
239
325
|
if (context.path.at(-1)?.type !== 'Program') {
|
|
240
326
|
// fatal since we don't have a transformation defined for this case
|
|
241
327
|
error(
|
|
242
|
-
'`#
|
|
328
|
+
'`#server` block can only be declared at the module level.',
|
|
243
329
|
context.state.analysis.module.filename,
|
|
244
330
|
node,
|
|
245
331
|
);
|
|
@@ -291,6 +377,8 @@ const visitors = {
|
|
|
291
377
|
if (
|
|
292
378
|
binding.kind === 'prop' ||
|
|
293
379
|
binding.kind === 'prop_fallback' ||
|
|
380
|
+
binding.kind === 'lazy' ||
|
|
381
|
+
binding.kind === 'lazy_fallback' ||
|
|
294
382
|
binding.kind === 'for_pattern' ||
|
|
295
383
|
(is_reference(node, /** @type {AST.Node} */ (parent)) &&
|
|
296
384
|
node.tracked &&
|
|
@@ -303,45 +391,6 @@ const visitors = {
|
|
|
303
391
|
}
|
|
304
392
|
}
|
|
305
393
|
|
|
306
|
-
// Validate #ripple namespace usage
|
|
307
|
-
const source_name = node.metadata?.source_name;
|
|
308
|
-
if (typeof source_name === 'string' && source_name.startsWith('#ripple.')) {
|
|
309
|
-
// Cannot assign to a #ripple namespace identifier (left side)
|
|
310
|
-
if (
|
|
311
|
-
(parent?.type === 'AssignmentExpression' && parent.left === node) ||
|
|
312
|
-
parent?.type === 'UpdateExpression'
|
|
313
|
-
) {
|
|
314
|
-
error(
|
|
315
|
-
`Cannot assign to \`${source_name}\`. The \`#ripple\` namespace is read-only.`,
|
|
316
|
-
context.state.analysis.module.filename,
|
|
317
|
-
node,
|
|
318
|
-
context.state.loose ? context.state.analysis.errors : undefined,
|
|
319
|
-
context.state.analysis.comments,
|
|
320
|
-
);
|
|
321
|
-
return context.next();
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// Valid: callee of a CallExpression
|
|
325
|
-
if (parent?.type === 'CallExpression' && parent.callee === node) {
|
|
326
|
-
return context.next();
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// Valid: object of a MemberExpression (further validated in MemberExpression visitor)
|
|
330
|
-
if (parent?.type === 'MemberExpression' && parent.object === node) {
|
|
331
|
-
return context.next();
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// Everything else is an invalid bare reference
|
|
335
|
-
error(
|
|
336
|
-
`\`${source_name}\` must be called as a function, e.g., \`${source_name}(...)\`.`,
|
|
337
|
-
context.state.analysis.module.filename,
|
|
338
|
-
node,
|
|
339
|
-
context.state.loose ? context.state.analysis.errors : undefined,
|
|
340
|
-
context.state.analysis.comments,
|
|
341
|
-
);
|
|
342
|
-
return context.next();
|
|
343
|
-
}
|
|
344
|
-
|
|
345
394
|
context.next();
|
|
346
395
|
},
|
|
347
396
|
|
|
@@ -358,13 +407,13 @@ const visitors = {
|
|
|
358
407
|
context.state.metadata.tracking = true;
|
|
359
408
|
}
|
|
360
409
|
|
|
361
|
-
// Track #
|
|
410
|
+
// Track #style.className or #style['className'] references
|
|
362
411
|
if (node.object.type === 'StyleIdentifier') {
|
|
363
412
|
const component = is_inside_component(context, true);
|
|
364
413
|
|
|
365
414
|
if (!component) {
|
|
366
415
|
error(
|
|
367
|
-
'`#
|
|
416
|
+
'`#style` can only be used within a component',
|
|
368
417
|
context.state.analysis.module.filename,
|
|
369
418
|
node,
|
|
370
419
|
context.state.loose ? context.state.analysis.errors : undefined,
|
|
@@ -378,19 +427,19 @@ const visitors = {
|
|
|
378
427
|
let className = null;
|
|
379
428
|
|
|
380
429
|
if (!node.computed && node.property.type === 'Identifier') {
|
|
381
|
-
// #
|
|
430
|
+
// #style.test
|
|
382
431
|
className = node.property.name;
|
|
383
432
|
} else if (
|
|
384
433
|
node.computed &&
|
|
385
434
|
node.property.type === 'Literal' &&
|
|
386
435
|
typeof node.property.value === 'string'
|
|
387
436
|
) {
|
|
388
|
-
// #
|
|
437
|
+
// #style['test']
|
|
389
438
|
className = node.property.value;
|
|
390
439
|
} else {
|
|
391
|
-
// #
|
|
440
|
+
// #style[expression] - dynamic, not allowed
|
|
392
441
|
error(
|
|
393
|
-
'`#
|
|
442
|
+
'`#style` property access must use a dot property or static string for css class name, not a dynamic expression',
|
|
394
443
|
context.state.analysis.module.filename,
|
|
395
444
|
node.property,
|
|
396
445
|
context.state.loose ? context.state.analysis.errors : undefined,
|
|
@@ -407,71 +456,6 @@ const visitors = {
|
|
|
407
456
|
context.state.analysis.metadata.serverIdentifierPresent = true;
|
|
408
457
|
}
|
|
409
458
|
|
|
410
|
-
// Validate #ripple namespace member access
|
|
411
|
-
if (
|
|
412
|
-
node.object.type === 'Identifier' &&
|
|
413
|
-
typeof node.object.metadata?.source_name === 'string' &&
|
|
414
|
-
node.object.metadata.source_name.startsWith('#ripple.')
|
|
415
|
-
) {
|
|
416
|
-
const ripple_source = node.object.metadata.source_name;
|
|
417
|
-
const member_parent = context.path.at(-1);
|
|
418
|
-
|
|
419
|
-
// No computed property access on #ripple namespace
|
|
420
|
-
if (node.computed) {
|
|
421
|
-
error(
|
|
422
|
-
`Computed property access is not allowed on \`${ripple_source}\`. Use dot notation instead.`,
|
|
423
|
-
context.state.analysis.module.filename,
|
|
424
|
-
node,
|
|
425
|
-
context.state.loose ? context.state.analysis.errors : undefined,
|
|
426
|
-
context.state.analysis.comments,
|
|
427
|
-
);
|
|
428
|
-
return context.next();
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
if (ripple_source === '#ripple.array') {
|
|
432
|
-
// Only .from, .of, and .fromAsync are allowed on #ripple.array
|
|
433
|
-
const allowed_methods = new Set(['from', 'of', 'fromAsync']);
|
|
434
|
-
const prop_name = node.property.type === 'Identifier' ? node.property.name : null;
|
|
435
|
-
|
|
436
|
-
if (prop_name === null || !allowed_methods.has(prop_name)) {
|
|
437
|
-
error(
|
|
438
|
-
`Only \`.from\`, \`.of\`, and \`.fromAsync\` are allowed on \`#ripple.array\`.${prop_name ? ` Got \`.${prop_name}\`.` : ''}`,
|
|
439
|
-
context.state.analysis.module.filename,
|
|
440
|
-
node.property,
|
|
441
|
-
context.state.loose ? context.state.analysis.errors : undefined,
|
|
442
|
-
context.state.analysis.comments,
|
|
443
|
-
);
|
|
444
|
-
return context.next();
|
|
445
|
-
}
|
|
446
|
-
} else {
|
|
447
|
-
// No member access allowed for other #ripple namespaces
|
|
448
|
-
error(
|
|
449
|
-
`Member access is not allowed on \`${ripple_source}\`. Use \`${ripple_source}(...)\` to call it directly.`,
|
|
450
|
-
context.state.analysis.module.filename,
|
|
451
|
-
node,
|
|
452
|
-
context.state.loose ? context.state.analysis.errors : undefined,
|
|
453
|
-
context.state.analysis.comments,
|
|
454
|
-
);
|
|
455
|
-
return context.next();
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// All #ripple member expressions must be called as a function
|
|
459
|
-
if (!(member_parent?.type === 'CallExpression' && member_parent.callee === node)) {
|
|
460
|
-
const prop_name = node.property.type === 'Identifier' ? node.property.name : null;
|
|
461
|
-
const full_name = prop_name ? `${ripple_source}.${prop_name}` : ripple_source;
|
|
462
|
-
error(
|
|
463
|
-
`\`${full_name}\` must be called as a function, e.g., \`${full_name}(...)\`.`,
|
|
464
|
-
context.state.analysis.module.filename,
|
|
465
|
-
node,
|
|
466
|
-
context.state.loose ? context.state.analysis.errors : undefined,
|
|
467
|
-
context.state.analysis.comments,
|
|
468
|
-
);
|
|
469
|
-
return context.next();
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
return context.next();
|
|
473
|
-
}
|
|
474
|
-
|
|
475
459
|
if (node.object.type === 'Identifier' && !node.object.tracked) {
|
|
476
460
|
const binding = context.state.scope.get(node.object.name);
|
|
477
461
|
|
|
@@ -544,38 +528,6 @@ const visitors = {
|
|
|
544
528
|
},
|
|
545
529
|
|
|
546
530
|
NewExpression(node, context) {
|
|
547
|
-
const callee = node.callee;
|
|
548
|
-
|
|
549
|
-
// Cannot use `new` with #ripple namespace
|
|
550
|
-
if (
|
|
551
|
-
callee.type === 'Identifier' &&
|
|
552
|
-
typeof callee.metadata?.source_name === 'string' &&
|
|
553
|
-
callee.metadata.source_name.startsWith('#ripple.')
|
|
554
|
-
) {
|
|
555
|
-
error(
|
|
556
|
-
`Cannot use \`new\` with \`${callee.metadata.source_name}\`. Use \`${callee.metadata.source_name}(...)\` instead.`,
|
|
557
|
-
context.state.analysis.module.filename,
|
|
558
|
-
node,
|
|
559
|
-
context.state.loose ? context.state.analysis.errors : undefined,
|
|
560
|
-
context.state.analysis.comments,
|
|
561
|
-
);
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
if (
|
|
565
|
-
callee.type === 'MemberExpression' &&
|
|
566
|
-
callee.object.type === 'Identifier' &&
|
|
567
|
-
typeof callee.object.metadata?.source_name === 'string' &&
|
|
568
|
-
callee.object.metadata.source_name.startsWith('#ripple.')
|
|
569
|
-
) {
|
|
570
|
-
error(
|
|
571
|
-
`Cannot use \`new\` with the \`#ripple\` namespace.`,
|
|
572
|
-
context.state.analysis.module.filename,
|
|
573
|
-
node,
|
|
574
|
-
context.state.loose ? context.state.analysis.errors : undefined,
|
|
575
|
-
context.state.analysis.comments,
|
|
576
|
-
);
|
|
577
|
-
}
|
|
578
|
-
|
|
579
531
|
context.next();
|
|
580
532
|
},
|
|
581
533
|
|
|
@@ -611,6 +563,18 @@ const visitors = {
|
|
|
611
563
|
}
|
|
612
564
|
visit(declarator, state);
|
|
613
565
|
} else {
|
|
566
|
+
// Handle lazy destructuring patterns
|
|
567
|
+
if (
|
|
568
|
+
(declarator.id.type === 'ObjectPattern' || declarator.id.type === 'ArrayPattern') &&
|
|
569
|
+
declarator.id.lazy
|
|
570
|
+
) {
|
|
571
|
+
const lazy_id = b.id(state.scope.generate('lazy'));
|
|
572
|
+
const writable = node.kind !== 'const';
|
|
573
|
+
setup_lazy_transforms(declarator.id, lazy_id, state, writable);
|
|
574
|
+
// Store the generated identifier name on the pattern for the transform phase
|
|
575
|
+
declarator.id.metadata = { ...declarator.id.metadata, lazy_id: lazy_id.name };
|
|
576
|
+
}
|
|
577
|
+
|
|
614
578
|
const paths = extract_paths(declarator.id);
|
|
615
579
|
|
|
616
580
|
for (const path of paths) {
|
|
@@ -640,10 +604,10 @@ const visitors = {
|
|
|
640
604
|
component.metadata.styleIdentifierPresent = true;
|
|
641
605
|
}
|
|
642
606
|
|
|
643
|
-
// #
|
|
607
|
+
// #style must only be used for property access (e.g., #style.className)
|
|
644
608
|
if (!parent || parent.type !== 'MemberExpression' || parent.object !== node) {
|
|
645
609
|
error(
|
|
646
|
-
'`#
|
|
610
|
+
'`#style` can only be used for property access, e.g., `#style.className`.',
|
|
647
611
|
context.state.analysis.module.filename,
|
|
648
612
|
node,
|
|
649
613
|
context.state.loose ? context.state.analysis.errors : undefined,
|
|
@@ -656,10 +620,10 @@ const visitors = {
|
|
|
656
620
|
ServerIdentifier(node, context) {
|
|
657
621
|
const parent = context.path.at(-1);
|
|
658
622
|
|
|
659
|
-
// #
|
|
623
|
+
// #server must only be used for member access (e.g., #server.functionName(...))
|
|
660
624
|
if (!parent || parent.type !== 'MemberExpression' || parent.object !== node) {
|
|
661
625
|
error(
|
|
662
|
-
'`#
|
|
626
|
+
'`#server` can only be used for member access, e.g., `#server.functionName(...)`.',
|
|
663
627
|
context.state.analysis.module.filename,
|
|
664
628
|
node,
|
|
665
629
|
context.state.loose ? context.state.analysis.errors : undefined,
|
|
@@ -685,32 +649,9 @@ const visitors = {
|
|
|
685
649
|
if (node.params.length > 0) {
|
|
686
650
|
const props = node.params[0];
|
|
687
651
|
|
|
688
|
-
if (props.type === 'ObjectPattern') {
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
for (const path of paths) {
|
|
692
|
-
const name = /** @type {AST.Identifier} */ (path.node).name;
|
|
693
|
-
const binding = context.state.scope.get(name);
|
|
694
|
-
|
|
695
|
-
if (binding !== null) {
|
|
696
|
-
binding.kind = path.has_default_value ? 'prop_fallback' : 'prop';
|
|
697
|
-
|
|
698
|
-
binding.transform = {
|
|
699
|
-
read: (_) => {
|
|
700
|
-
return path.expression(b.id('__props'));
|
|
701
|
-
},
|
|
702
|
-
assign: (node, value) => {
|
|
703
|
-
return b.assignment(
|
|
704
|
-
'=',
|
|
705
|
-
/** @type {AST.MemberExpression} */ (path.expression(b.id('__props'))),
|
|
706
|
-
value,
|
|
707
|
-
);
|
|
708
|
-
},
|
|
709
|
-
update: (node) =>
|
|
710
|
-
b.update(node.operator, path.expression(b.id('__props')), node.prefix),
|
|
711
|
-
};
|
|
712
|
-
}
|
|
713
|
-
}
|
|
652
|
+
if ((props.type === 'ObjectPattern' || props.type === 'ArrayPattern') && props.lazy) {
|
|
653
|
+
// Lazy destructuring: &{...} or &[...] — set up lazy transforms
|
|
654
|
+
setup_lazy_transforms(props, b.id('__props'), context.state, true);
|
|
714
655
|
} else if (props.type === 'AssignmentPattern') {
|
|
715
656
|
error(
|
|
716
657
|
'Props are always an object, use destructured props with default values instead',
|
|
@@ -1408,7 +1349,7 @@ const visitors = {
|
|
|
1408
1349
|
attr.value.object.type === 'StyleIdentifier'
|
|
1409
1350
|
) {
|
|
1410
1351
|
error(
|
|
1411
|
-
'`#
|
|
1352
|
+
'`#style` cannot be used directly on DOM elements. Pass the class to a child component instead.',
|
|
1412
1353
|
state.analysis.module.filename,
|
|
1413
1354
|
attr.value.object,
|
|
1414
1355
|
context.state.loose ? context.state.analysis.errors : undefined,
|
|
@@ -1078,11 +1078,11 @@ export function prune_css(css, element, styleClasses, topScopedClasses) {
|
|
|
1078
1078
|
node.metadata.used = true;
|
|
1079
1079
|
}
|
|
1080
1080
|
|
|
1081
|
-
// Populate top_scoped_classes for truly standalone class selectors (for #
|
|
1081
|
+
// Populate top_scoped_classes for truly standalone class selectors (for #style support).
|
|
1082
1082
|
// A class is standalone only when the entire effective selector chain (after resolving
|
|
1083
1083
|
// nesting and stripping :global) is a single RelativeSelector with a single ClassSelector.
|
|
1084
1084
|
// This prevents classes from compound selectors like `.wrapper .nested` or selectors
|
|
1085
|
-
// inside :global() from being treated as valid #
|
|
1085
|
+
// inside :global() from being treated as valid #style targets.
|
|
1086
1086
|
if (selectors.length === 1) {
|
|
1087
1087
|
const sole_selector = selectors[0];
|
|
1088
1088
|
if (
|