ripple 0.2.46 → 0.2.48

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.
Files changed (38) hide show
  1. package/package.json +1 -1
  2. package/src/compiler/phases/1-parse/index.js +52 -2
  3. package/src/compiler/phases/2-analyze/index.js +640 -667
  4. package/src/compiler/phases/3-transform/index.js +1878 -1879
  5. package/src/compiler/phases/3-transform/segments.js +2 -2
  6. package/src/compiler/utils.js +598 -550
  7. package/src/jsx-runtime.js +12 -12
  8. package/src/runtime/array.js +611 -609
  9. package/src/runtime/index.js +29 -17
  10. package/src/runtime/internal/client/array.js +121 -121
  11. package/src/runtime/internal/client/blocks.js +206 -206
  12. package/src/runtime/internal/client/constants.js +2 -2
  13. package/src/runtime/internal/client/context.js +40 -40
  14. package/src/runtime/internal/client/events.js +191 -191
  15. package/src/runtime/internal/client/for.js +355 -355
  16. package/src/runtime/internal/client/if.js +25 -25
  17. package/src/runtime/internal/client/index.js +57 -56
  18. package/src/runtime/internal/client/operations.js +32 -32
  19. package/src/runtime/internal/client/portal.js +19 -19
  20. package/src/runtime/internal/client/render.js +132 -132
  21. package/src/runtime/internal/client/runtime.js +839 -835
  22. package/src/runtime/internal/client/template.js +36 -36
  23. package/src/runtime/internal/client/try.js +113 -113
  24. package/src/runtime/internal/client/types.d.ts +12 -11
  25. package/src/runtime/internal/client/utils.js +5 -5
  26. package/src/runtime/map.js +139 -139
  27. package/src/runtime/set.js +130 -130
  28. package/src/utils/ast.js +189 -189
  29. package/src/utils/builders.js +244 -244
  30. package/src/utils/sanitize_template_string.js +1 -1
  31. package/tests/__snapshots__/composite.test.ripple.snap +1 -1
  32. package/tests/accessors-props.test.ripple +9 -9
  33. package/tests/basic.test.ripple +4 -4
  34. package/tests/boundaries.test.ripple +17 -17
  35. package/tests/compiler.test.ripple +14 -14
  36. package/tests/composite.test.ripple +43 -72
  37. package/tests/context.test.ripple +35 -12
  38. package/types/index.d.ts +38 -34
@@ -4,22 +4,22 @@ import * as b from '../utils/builders.js';
4
4
  const regex_return_characters = /\r/g;
5
5
 
6
6
  const VOID_ELEMENT_NAMES = [
7
- 'area',
8
- 'base',
9
- 'br',
10
- 'col',
11
- 'command',
12
- 'embed',
13
- 'hr',
14
- 'img',
15
- 'input',
16
- 'keygen',
17
- 'link',
18
- 'meta',
19
- 'param',
20
- 'source',
21
- 'track',
22
- 'wbr',
7
+ 'area',
8
+ 'base',
9
+ 'br',
10
+ 'col',
11
+ 'command',
12
+ 'embed',
13
+ 'hr',
14
+ 'img',
15
+ 'input',
16
+ 'keygen',
17
+ 'link',
18
+ 'meta',
19
+ 'param',
20
+ 'source',
21
+ 'track',
22
+ 'wbr',
23
23
  ];
24
24
 
25
25
  /**
@@ -27,634 +27,682 @@ const VOID_ELEMENT_NAMES = [
27
27
  * @param {string} name
28
28
  */
29
29
  export function is_void_element(name) {
30
- return VOID_ELEMENT_NAMES.includes(name) || name.toLowerCase() === '!doctype';
30
+ return VOID_ELEMENT_NAMES.includes(name) || name.toLowerCase() === '!doctype';
31
31
  }
32
32
 
33
33
  const RESERVED_WORDS = [
34
- 'arguments',
35
- 'await',
36
- 'break',
37
- 'case',
38
- 'catch',
39
- 'class',
40
- 'const',
41
- 'continue',
42
- 'debugger',
43
- 'default',
44
- 'delete',
45
- 'do',
46
- 'else',
47
- 'enum',
48
- 'eval',
49
- 'export',
50
- 'extends',
51
- 'false',
52
- 'finally',
53
- 'for',
54
- 'function',
55
- 'if',
56
- 'implements',
57
- 'import',
58
- 'in',
59
- 'instanceof',
60
- 'interface',
61
- 'let',
62
- 'new',
63
- 'null',
64
- 'package',
65
- 'private',
66
- 'protected',
67
- 'public',
68
- 'return',
69
- 'static',
70
- 'super',
71
- 'switch',
72
- 'this',
73
- 'throw',
74
- 'true',
75
- 'try',
76
- 'typeof',
77
- 'var',
78
- 'void',
79
- 'while',
80
- 'with',
81
- 'yield',
34
+ 'arguments',
35
+ 'await',
36
+ 'break',
37
+ 'case',
38
+ 'catch',
39
+ 'class',
40
+ 'const',
41
+ 'continue',
42
+ 'debugger',
43
+ 'default',
44
+ 'delete',
45
+ 'do',
46
+ 'else',
47
+ 'enum',
48
+ 'eval',
49
+ 'export',
50
+ 'extends',
51
+ 'false',
52
+ 'finally',
53
+ 'for',
54
+ 'function',
55
+ 'if',
56
+ 'implements',
57
+ 'import',
58
+ 'in',
59
+ 'instanceof',
60
+ 'interface',
61
+ 'let',
62
+ 'new',
63
+ 'null',
64
+ 'package',
65
+ 'private',
66
+ 'protected',
67
+ 'public',
68
+ 'return',
69
+ 'static',
70
+ 'super',
71
+ 'switch',
72
+ 'this',
73
+ 'throw',
74
+ 'true',
75
+ 'try',
76
+ 'typeof',
77
+ 'var',
78
+ 'void',
79
+ 'while',
80
+ 'with',
81
+ 'yield',
82
82
  ];
83
83
 
84
84
  export function is_reserved(word) {
85
- return RESERVED_WORDS.includes(word);
85
+ return RESERVED_WORDS.includes(word);
86
86
  }
87
87
 
88
88
  /**
89
89
  * Attributes that are boolean, i.e. they are present or not present.
90
90
  */
91
91
  const DOM_BOOLEAN_ATTRIBUTES = [
92
- 'allowfullscreen',
93
- 'async',
94
- 'autofocus',
95
- 'autoplay',
96
- 'checked',
97
- 'controls',
98
- 'default',
99
- 'disabled',
100
- 'formnovalidate',
101
- 'hidden',
102
- 'indeterminate',
103
- 'inert',
104
- 'ismap',
105
- 'loop',
106
- 'multiple',
107
- 'muted',
108
- 'nomodule',
109
- 'novalidate',
110
- 'open',
111
- 'playsinline',
112
- 'readonly',
113
- 'required',
114
- 'reversed',
115
- 'seamless',
116
- 'selected',
117
- 'webkitdirectory',
118
- 'defer',
119
- 'disablepictureinpicture',
120
- 'disableremoteplayback',
92
+ 'allowfullscreen',
93
+ 'async',
94
+ 'autofocus',
95
+ 'autoplay',
96
+ 'checked',
97
+ 'controls',
98
+ 'default',
99
+ 'disabled',
100
+ 'formnovalidate',
101
+ 'hidden',
102
+ 'indeterminate',
103
+ 'inert',
104
+ 'ismap',
105
+ 'loop',
106
+ 'multiple',
107
+ 'muted',
108
+ 'nomodule',
109
+ 'novalidate',
110
+ 'open',
111
+ 'playsinline',
112
+ 'readonly',
113
+ 'required',
114
+ 'reversed',
115
+ 'seamless',
116
+ 'selected',
117
+ 'webkitdirectory',
118
+ 'defer',
119
+ 'disablepictureinpicture',
120
+ 'disableremoteplayback',
121
121
  ];
122
122
 
123
123
  export function is_boolean_attribute(name) {
124
- return DOM_BOOLEAN_ATTRIBUTES.includes(name);
124
+ return DOM_BOOLEAN_ATTRIBUTES.includes(name);
125
125
  }
126
126
 
127
127
  const DOM_PROPERTIES = [
128
- ...DOM_BOOLEAN_ATTRIBUTES,
129
- 'formNoValidate',
130
- 'isMap',
131
- 'noModule',
132
- 'playsInline',
133
- 'readOnly',
134
- 'value',
135
- 'volume',
136
- 'defaultValue',
137
- 'defaultChecked',
138
- 'srcObject',
139
- 'noValidate',
140
- 'allowFullscreen',
141
- 'disablePictureInPicture',
142
- 'disableRemotePlayback',
128
+ ...DOM_BOOLEAN_ATTRIBUTES,
129
+ 'formNoValidate',
130
+ 'isMap',
131
+ 'noModule',
132
+ 'playsInline',
133
+ 'readOnly',
134
+ 'value',
135
+ 'volume',
136
+ 'defaultValue',
137
+ 'defaultChecked',
138
+ 'srcObject',
139
+ 'noValidate',
140
+ 'allowFullscreen',
141
+ 'disablePictureInPicture',
142
+ 'disableRemotePlayback',
143
143
  ];
144
144
 
145
145
  export function is_dom_property(name) {
146
- return DOM_PROPERTIES.includes(name);
146
+ return DOM_PROPERTIES.includes(name);
147
147
  }
148
148
 
149
149
  /** List of Element events that will be delegated */
150
150
  const DELEGATED_EVENTS = [
151
- 'beforeinput',
152
- 'click',
153
- 'change',
154
- 'dblclick',
155
- 'contextmenu',
156
- 'focusin',
157
- 'focusout',
158
- 'input',
159
- 'keydown',
160
- 'keyup',
161
- 'mousedown',
162
- 'mousemove',
163
- 'mouseout',
164
- 'mouseover',
165
- 'mouseup',
166
- 'pointerdown',
167
- 'pointermove',
168
- 'pointerout',
169
- 'pointerover',
170
- 'pointerup',
171
- 'touchend',
172
- 'touchmove',
173
- 'touchstart',
151
+ 'beforeinput',
152
+ 'click',
153
+ 'change',
154
+ 'dblclick',
155
+ 'contextmenu',
156
+ 'focusin',
157
+ 'focusout',
158
+ 'input',
159
+ 'keydown',
160
+ 'keyup',
161
+ 'mousedown',
162
+ 'mousemove',
163
+ 'mouseout',
164
+ 'mouseover',
165
+ 'mouseup',
166
+ 'pointerdown',
167
+ 'pointermove',
168
+ 'pointerout',
169
+ 'pointerover',
170
+ 'pointerup',
171
+ 'touchend',
172
+ 'touchmove',
173
+ 'touchstart',
174
174
  ];
175
175
 
176
176
  export function is_delegated(event_name) {
177
- return DELEGATED_EVENTS.includes(event_name);
177
+ return DELEGATED_EVENTS.includes(event_name);
178
178
  }
179
179
 
180
180
  const PASSIVE_EVENTS = ['touchstart', 'touchmove'];
181
181
 
182
182
  export function is_passive_event(name) {
183
- return PASSIVE_EVENTS.includes(name);
183
+ return PASSIVE_EVENTS.includes(name);
184
184
  }
185
185
 
186
186
  export function is_event_attribute(attr) {
187
- return attr.startsWith('on') && attr.length > 2 && attr[2] === attr[2].toUpperCase();
187
+ return attr.startsWith('on') && attr.length > 2 && attr[2] === attr[2].toUpperCase();
188
188
  }
189
189
 
190
190
  const unhoisted = { hoisted: false };
191
191
 
192
192
  export function get_delegated_event(event_name, handler, state) {
193
- // Handle delegated event handlers. Bail out if not a delegated event.
194
- if (!handler || !is_delegated(event_name)) {
195
- return null;
196
- }
197
-
198
- /** @type {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression | null} */
199
- let target_function = null;
200
- let binding = null;
201
-
202
- if (handler.type === 'ArrowFunctionExpression' || handler.type === 'FunctionExpression') {
203
- target_function = handler;
204
- } else if (handler.type === 'Identifier') {
205
- binding = state.scope.get(handler.name);
206
-
207
- if (state.analysis.module.scope.references.has(handler.name)) {
208
- // If a binding with the same name is referenced in the module scope (even if not declared there), bail out
209
- return unhoisted;
210
- }
211
-
212
- if (binding != null) {
213
- for (const { path } of binding.references) {
214
- const parent = path.at(-1);
215
- if (parent === undefined) return unhoisted;
216
-
217
- const grandparent = path.at(-2);
218
-
219
- /** @type {AST.RegularElement | null} */
220
- let element = null;
221
- /** @type {string | null} */
222
- let event_name = null;
223
- if (
224
- parent.type === 'ExpressionTag' &&
225
- grandparent?.type === 'Attribute' &&
226
- is_event_attribute(grandparent)
227
- ) {
228
- element = /** @type {AST.RegularElement} */ (path.at(-3));
229
- const attribute = /** @type {AST.Attribute} */ (grandparent);
230
- event_name = get_attribute_event_name(attribute.name);
231
- }
232
-
233
- if (element && event_name) {
234
- if (
235
- element.type !== 'Element' ||
236
- element.metadata.has_spread ||
237
- !is_delegated(event_name)
238
- ) {
239
- return unhoisted;
240
- }
241
- } else if (parent.type !== 'FunctionDeclaration' && parent.type !== 'VariableDeclarator') {
242
- return unhoisted;
243
- }
244
- }
245
- }
246
-
247
- // If the binding is exported, bail out
248
- if (state.analysis.exports.find((node) => node.name === handler.name)) {
249
- return unhoisted;
250
- }
251
-
252
- if (binding !== null && binding.initial !== null && !binding.updated && !binding.is_called) {
253
- const binding_type = binding.initial.type;
254
-
255
- if (
256
- binding_type === 'ArrowFunctionExpression' ||
257
- binding_type === 'FunctionDeclaration' ||
258
- binding_type === 'FunctionExpression'
259
- ) {
260
- target_function = binding.initial;
261
- }
262
- }
263
- }
264
-
265
- // If we can't find a function, or the function has multiple parameters, bail out
266
- if (target_function == null || target_function.params.length > 1) {
267
- return unhoisted;
268
- }
269
-
270
- const visited_references = new Set();
271
- const scope = target_function.metadata.scope;
272
- for (const [reference] of scope.references) {
273
- // Bail out if the arguments keyword is used or $host is referenced
274
- if (reference === 'arguments') return unhoisted;
275
-
276
- const binding = scope.get(reference);
277
- const local_binding = state.scope.get(reference);
278
-
279
- // If we are referencing a binding that is shadowed in another scope then bail out.
280
- if (local_binding !== null && binding !== null && local_binding.node !== binding.node) {
281
- return unhoisted;
282
- }
283
-
284
- if (
285
- binding !== null &&
286
- // Bail out if the the binding is a rest param
287
- (binding.declaration_kind === 'rest_param' || // or any normal not reactive bindings that are mutated.
288
- // Bail out if we reference anything from the EachBlock (for now) that mutates in non-runes mode,
289
- (binding.kind === 'normal' && binding.updated))
290
- ) {
291
- return unhoisted;
292
- }
293
- visited_references.add(reference);
294
- }
295
-
296
- return { hoisted: true, function: target_function };
193
+ // Handle delegated event handlers. Bail out if not a delegated event.
194
+ if (!handler || !is_delegated(event_name)) {
195
+ return null;
196
+ }
197
+
198
+ /** @type {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression | null} */
199
+ let target_function = null;
200
+ let binding = null;
201
+
202
+ if (handler.type === 'ArrowFunctionExpression' || handler.type === 'FunctionExpression') {
203
+ target_function = handler;
204
+ } else if (handler.type === 'Identifier') {
205
+ binding = state.scope.get(handler.name);
206
+
207
+ if (state.analysis.module.scope.references.has(handler.name)) {
208
+ // If a binding with the same name is referenced in the module scope (even if not declared there), bail out
209
+ return unhoisted;
210
+ }
211
+
212
+ if (binding != null) {
213
+ for (const { path } of binding.references) {
214
+ const parent = path.at(-1);
215
+ if (parent === undefined) return unhoisted;
216
+
217
+ const grandparent = path.at(-2);
218
+
219
+ /** @type {AST.RegularElement | null} */
220
+ let element = null;
221
+ /** @type {string | null} */
222
+ let event_name = null;
223
+ if (
224
+ parent.type === 'ExpressionTag' &&
225
+ grandparent?.type === 'Attribute' &&
226
+ is_event_attribute(grandparent)
227
+ ) {
228
+ element = /** @type {AST.RegularElement} */ (path.at(-3));
229
+ const attribute = /** @type {AST.Attribute} */ (grandparent);
230
+ event_name = get_attribute_event_name(attribute.name);
231
+ }
232
+
233
+ if (element && event_name) {
234
+ if (
235
+ element.type !== 'Element' ||
236
+ element.metadata.has_spread ||
237
+ !is_delegated(event_name)
238
+ ) {
239
+ return unhoisted;
240
+ }
241
+ } else if (parent.type !== 'FunctionDeclaration' && parent.type !== 'VariableDeclarator') {
242
+ return unhoisted;
243
+ }
244
+ }
245
+ }
246
+
247
+ // If the binding is exported, bail out
248
+ if (state.analysis.exports.find((node) => node.name === handler.name)) {
249
+ return unhoisted;
250
+ }
251
+
252
+ if (binding !== null && binding.initial !== null && !binding.updated && !binding.is_called) {
253
+ const binding_type = binding.initial.type;
254
+
255
+ if (
256
+ binding_type === 'ArrowFunctionExpression' ||
257
+ binding_type === 'FunctionDeclaration' ||
258
+ binding_type === 'FunctionExpression'
259
+ ) {
260
+ target_function = binding.initial;
261
+ }
262
+ }
263
+ }
264
+
265
+ // If we can't find a function, or the function has multiple parameters, bail out
266
+ if (target_function == null || target_function.params.length > 1) {
267
+ return unhoisted;
268
+ }
269
+
270
+ const visited_references = new Set();
271
+ const scope = target_function.metadata.scope;
272
+ for (const [reference] of scope.references) {
273
+ // Bail out if the arguments keyword is used or $host is referenced
274
+ if (reference === 'arguments') return unhoisted;
275
+
276
+ const binding = scope.get(reference);
277
+ const local_binding = state.scope.get(reference);
278
+
279
+ // If we are referencing a binding that is shadowed in another scope then bail out.
280
+ if (local_binding !== null && binding !== null && local_binding.node !== binding.node) {
281
+ return unhoisted;
282
+ }
283
+
284
+ if (
285
+ binding !== null &&
286
+ // Bail out if the the binding is a rest param
287
+ (binding.declaration_kind === 'rest_param' || // or any normal not reactive bindings that are mutated.
288
+ // Bail out if we reference anything from the EachBlock (for now) that mutates in non-runes mode,
289
+ (binding.kind === 'normal' && binding.updated))
290
+ ) {
291
+ return unhoisted;
292
+ }
293
+ visited_references.add(reference);
294
+ }
295
+
296
+ return { hoisted: true, function: target_function };
297
297
  }
298
298
 
299
299
  function get_hoisted_params(node, context) {
300
- const scope = context.state.scope;
301
-
302
- /** @type {Identifier[]} */
303
- const params = [];
304
-
305
- /**
306
- * We only want to push if it's not already present to avoid name clashing
307
- * @param {Identifier} id
308
- */
309
- function push_unique(id) {
310
- if (!params.find((param) => param.name === id.name)) {
311
- params.push(id);
312
- }
313
- }
314
-
315
- for (const [reference] of scope.references) {
316
- let binding = scope.get(reference);
317
-
318
- if (binding !== null && !scope.declarations.has(reference) && binding.initial !== node) {
319
- if (binding.kind === 'prop') {
320
- push_unique(b.id('__props'));
321
- } else if (binding.kind === 'prop_fallback') {
322
- push_unique(b.id(binding.node.name));
323
- } else if (
324
- // imports don't need to be hoisted
325
- binding.declaration_kind !== 'import'
326
- ) {
327
- // create a copy to remove start/end tags which would mess up source maps
328
- push_unique(b.id(binding.node.name));
329
- }
330
- }
331
- }
332
- return params;
300
+ const scope = context.state.scope;
301
+
302
+ /** @type {Identifier[]} */
303
+ const params = [];
304
+
305
+ /**
306
+ * We only want to push if it's not already present to avoid name clashing
307
+ * @param {Identifier} id
308
+ */
309
+ function push_unique(id) {
310
+ if (!params.find((param) => param.name === id.name)) {
311
+ params.push(id);
312
+ }
313
+ }
314
+
315
+ for (const [reference] of scope.references) {
316
+ let binding = scope.get(reference);
317
+
318
+ if (binding !== null && !scope.declarations.has(reference) && binding.initial !== node) {
319
+ if (binding.kind === 'prop') {
320
+ push_unique(b.id('__props'));
321
+ } else if (binding.kind === 'prop_fallback') {
322
+ push_unique(b.id(binding.node.name));
323
+ } else if (
324
+ // imports don't need to be hoisted
325
+ binding.declaration_kind !== 'import'
326
+ ) {
327
+ // create a copy to remove start/end tags which would mess up source maps
328
+ push_unique(b.id(binding.node.name));
329
+ }
330
+ }
331
+ }
332
+ return params;
333
333
  }
334
334
 
335
335
  export function build_hoisted_params(node, context) {
336
- const hoisted_params = get_hoisted_params(node, context);
337
- node.metadata.hoisted_params = hoisted_params;
338
-
339
- /** @type {Pattern[]} */
340
- const params = [];
341
-
342
- if (node.params.length === 0) {
343
- if (hoisted_params.length > 0) {
344
- // For the event object
345
- params.push(b.id(context.state.scope.generate('_')));
346
- }
347
- } else {
348
- for (const param of node.params) {
349
- params.push(/** @type {Pattern} */ (context.visit(param)));
350
- }
351
- }
352
-
353
- params.push(...hoisted_params, b.id('__block'));
354
- return params;
336
+ const hoisted_params = get_hoisted_params(node, context);
337
+ node.metadata.hoisted_params = hoisted_params;
338
+
339
+ /** @type {Pattern[]} */
340
+ const params = [];
341
+
342
+ if (node.params.length === 0) {
343
+ if (hoisted_params.length > 0) {
344
+ // For the event object
345
+ params.push(b.id(context.state.scope.generate('_')));
346
+ }
347
+ } else {
348
+ for (const param of node.params) {
349
+ params.push(/** @type {Pattern} */ (context.visit(param)));
350
+ }
351
+ }
352
+
353
+ params.push(...hoisted_params, b.id('__block'));
354
+ return params;
355
355
  }
356
356
 
357
357
  export function is_inside_component(context, includes_functions = false) {
358
- for (let i = context.path.length - 1; i >= 0; i -= 1) {
359
- const context_node = context.path[i];
360
- const type = context_node.type;
361
-
362
- if (
363
- !includes_functions &&
364
- (type === 'FunctionExpression' ||
365
- type === 'ArrowFunctionExpression' ||
366
- type === 'FunctionDeclaration')
367
- ) {
368
- return false;
369
- }
370
- if (type === 'Component') {
371
- return true;
372
- }
373
- }
374
- return false;
358
+ for (let i = context.path.length - 1; i >= 0; i -= 1) {
359
+ const context_node = context.path[i];
360
+ const type = context_node.type;
361
+
362
+ if (
363
+ !includes_functions &&
364
+ (type === 'FunctionExpression' ||
365
+ type === 'ArrowFunctionExpression' ||
366
+ type === 'FunctionDeclaration')
367
+ ) {
368
+ return false;
369
+ }
370
+ if (type === 'Component') {
371
+ return true;
372
+ }
373
+ }
374
+ return false;
375
375
  }
376
376
 
377
377
  export function is_component_level_function(context) {
378
- for (let i = context.path.length - 1; i >= 0; i -= 1) {
379
- const context_node = context.path[i];
380
- const type = context_node.type;
381
-
382
- if (type === 'BlockStatement') {
383
- if (context_node.body.find((n) => n.type === 'Component')) {
384
- return true;
385
- }
386
- debugger
387
-
388
- }
389
-
390
- if (
391
- type === 'FunctionExpression' ||
392
- type === 'ArrowFunctionExpression' ||
393
- type === 'FunctionDeclaration'
394
- ) {
395
- return false;
396
- }
397
- }
398
- return true;
378
+ for (let i = context.path.length - 1; i >= 0; i -= 1) {
379
+ const context_node = context.path[i];
380
+ const type = context_node.type;
381
+
382
+ if (type === 'BlockStatement' && context_node.body.find((n) => n.type === 'Component')) {
383
+ return true;
384
+ }
385
+
386
+ if (
387
+ type === 'FunctionExpression' ||
388
+ type === 'ArrowFunctionExpression' ||
389
+ type === 'FunctionDeclaration'
390
+ ) {
391
+ return false;
392
+ }
393
+ }
394
+ return true;
399
395
  }
400
396
 
401
397
  export function is_inside_call_expression(context) {
402
- for (let i = context.path.length - 1; i >= 0; i -= 1) {
403
- const context_node = context.path[i];
404
- const type = context_node.type;
405
-
406
- if (
407
- type === 'FunctionExpression' ||
408
- type === 'ArrowFunctionExpression' ||
409
- type === 'FunctionDeclaration'
410
- ) {
411
- return false;
412
- }
413
- if (type === 'CallExpression') {
414
- return true;
415
- }
416
- }
417
- return false;
398
+ for (let i = context.path.length - 1; i >= 0; i -= 1) {
399
+ const context_node = context.path[i];
400
+ const type = context_node.type;
401
+
402
+ if (
403
+ type === 'FunctionExpression' ||
404
+ type === 'ArrowFunctionExpression' ||
405
+ type === 'FunctionDeclaration'
406
+ ) {
407
+ return false;
408
+ }
409
+ if (type === 'CallExpression') {
410
+ return true;
411
+ }
412
+ }
413
+ return false;
418
414
  }
419
415
 
420
416
  export function is_tracked_name(name) {
421
- return typeof name === 'string' && name.startsWith('$') && name.length > 1 && name[1] !== '$';
417
+ return typeof name === 'string' && name.startsWith('$') && name.length > 1 && name[1] !== '$';
422
418
  }
423
419
 
424
420
  export function is_value_static(node) {
425
- if (node.type === 'Literal') {
426
- return true;
427
- }
428
- if (node.type === 'ArrayExpression') {
429
- return true;
430
- }
431
- if (node.type === 'NewExpression') {
432
- if (node.callee.type === 'Identifier' && node.callee.name === 'Array') {
433
- return true;
434
- }
435
- return false;
436
- }
437
-
438
- return false;
421
+ if (node.type === 'Literal') {
422
+ return true;
423
+ }
424
+ if (node.type === 'ArrayExpression') {
425
+ return true;
426
+ }
427
+ if (node.type === 'NewExpression') {
428
+ if (node.callee.type === 'Identifier' && node.callee.name === 'Array') {
429
+ return true;
430
+ }
431
+ return false;
432
+ }
433
+
434
+ return false;
439
435
  }
440
436
 
441
437
  export function is_tracked_computed_property(object, property, context) {
442
- const binding = context.state.scope.get(object.name);
443
-
444
- if (binding) {
445
- const initial = binding.initial;
446
- if (initial && is_value_static(initial)) {
447
- return false;
448
- }
449
- }
450
- if (property.type === 'Identifier') {
451
- return true;
452
- }
453
- if (
454
- property.type === 'Literal' &&
455
- typeof property.value === 'string' &&
456
- is_tracked_name(property.value)
457
- ) {
458
- return true;
459
- }
460
-
461
- // TODO: do we need to handle more logic here? default to false for now
462
- return true;
438
+ if (object.tracked) {
439
+ return false;
440
+ }
441
+ const binding = context.state.scope.get(object.name);
442
+
443
+ if (binding) {
444
+ const initial = binding.initial;
445
+ if (initial && is_value_static(initial)) {
446
+ return false;
447
+ }
448
+ }
449
+ if (property.type === 'Identifier') {
450
+ return true;
451
+ }
452
+ if (
453
+ property.type === 'Literal' &&
454
+ typeof property.value === 'string' &&
455
+ is_tracked_name(property.value)
456
+ ) {
457
+ return true;
458
+ }
459
+
460
+ // TODO: do we need to handle more logic here? default to false for now
461
+ return true;
463
462
  }
464
463
 
465
464
  export function is_ripple_import(callee, context) {
466
- if (callee.type === 'Identifier') {
467
- const binding = context.state.scope.get(callee.name);
465
+ if (callee.type === 'Identifier') {
466
+ const binding = context.state.scope.get(callee.name);
468
467
 
469
- return (
470
- binding?.declaration_kind === 'import' &&
471
- binding.initial.source.type === 'Literal' &&
472
- binding.initial.source.value === 'ripple'
473
- );
474
- }
468
+ return (
469
+ binding?.declaration_kind === 'import' &&
470
+ binding.initial.source.type === 'Literal' &&
471
+ binding.initial.source.value === 'ripple'
472
+ );
473
+ }
475
474
 
476
- return false;
475
+ return false;
477
476
  }
478
477
 
479
478
  export function is_declared_function_within_component(node, context) {
480
- const component = context.path.find((n) => n.type === 'Component');
481
-
482
- if (node.type === 'Identifier' && component) {
483
- const binding = context.state.scope.get(node.name);
484
- const component_scope = context.state.scopes.get(component);
485
-
486
- if (binding !== null && component_scope !== null) {
487
- if (
488
- binding.declaration_kind !== 'function' &&
489
- binding.initial?.type !== 'FunctionDeclaration' &&
490
- binding.initial?.type !== 'ArrowFunctionExpression' &&
491
- binding.initial?.type !== 'FunctionExpression'
492
- ) {
493
- return false;
494
- }
495
- let scope = binding.scope;
496
-
497
- while (scope !== null) {
498
- if (scope === component_scope) {
499
- return true;
500
- }
501
- scope = scope.parent;
502
- }
503
- }
504
- }
505
-
506
- return false;
479
+ const component = context.path.find((n) => n.type === 'Component');
480
+
481
+ if (node.type === 'Identifier' && component) {
482
+ const binding = context.state.scope.get(node.name);
483
+ const component_scope = context.state.scopes.get(component);
484
+
485
+ if (binding !== null && component_scope !== null) {
486
+ if (
487
+ binding.declaration_kind !== 'function' &&
488
+ binding.initial?.type !== 'FunctionDeclaration' &&
489
+ binding.initial?.type !== 'ArrowFunctionExpression' &&
490
+ binding.initial?.type !== 'FunctionExpression'
491
+ ) {
492
+ return false;
493
+ }
494
+ let scope = binding.scope;
495
+
496
+ while (scope !== null) {
497
+ if (scope === component_scope) {
498
+ return true;
499
+ }
500
+ scope = scope.parent;
501
+ }
502
+ }
503
+ }
504
+
505
+ return false;
507
506
  }
508
507
 
509
508
  function is_non_coercive_operator(operator) {
510
- return ['=', '||=', '&&=', '??='].includes(operator);
509
+ return ['=', '||=', '&&=', '??='].includes(operator);
511
510
  }
512
511
 
513
512
  export function visit_assignment_expression(node, context, build_assignment) {
514
- if (
515
- node.left.type === 'ArrayPattern' ||
516
- node.left.type === 'ObjectPattern' ||
517
- node.left.type === 'RestElement'
518
- ) {
519
- const value = /** @type {Expression} */ (context.visit(node.right));
520
- const should_cache = value.type !== 'Identifier';
521
- const rhs = should_cache ? b.id('$$value') : value;
522
-
523
- let changed = false;
524
-
525
- const assignments = extract_paths(node.left).map((path) => {
526
- const value = path.expression?.(rhs);
527
-
528
- let assignment = build_assignment('=', path.node, value, context);
529
- if (assignment !== null) changed = true;
530
-
531
- return (
532
- assignment ??
533
- b.assignment(
534
- '=',
535
- /** @type {Pattern} */ (context.visit(path.node)),
536
- /** @type {Expression} */ (context.visit(value)),
537
- )
538
- );
539
- });
540
-
541
- if (!changed) {
542
- // No change to output -> nothing to transform -> we can keep the original assignment
543
- return null;
544
- }
545
-
546
- const is_standalone = /** @type {Node} */ (context.path.at(-1)).type.endsWith('Statement');
547
- const sequence = b.sequence(assignments);
548
-
549
- if (!is_standalone) {
550
- // this is part of an expression, we need the sequence to end with the value
551
- sequence.expressions.push(rhs);
552
- }
553
-
554
- if (should_cache) {
555
- // the right hand side is a complex expression, wrap in an IIFE to cache it
556
- const iife = b.arrow([rhs], sequence);
557
-
558
- const iife_is_async =
559
- is_expression_async(value) ||
560
- assignments.some((assignment) => is_expression_async(assignment));
561
-
562
- return iife_is_async ? b.await(b.call(b.async(iife), value)) : b.call(iife, value);
563
- }
564
-
565
- return sequence;
566
- }
567
-
568
- if (node.left.type !== 'Identifier' && node.left.type !== 'MemberExpression') {
569
- throw new Error(`Unexpected assignment type ${node.left.type}`);
570
- }
571
-
572
- const transformed = build_assignment(node.operator, node.left, node.right, context);
573
-
574
- if (transformed === node.left) {
575
- return node;
576
- }
577
-
578
- return transformed;
513
+ if (
514
+ node.left.type === 'ArrayPattern' ||
515
+ node.left.type === 'ObjectPattern' ||
516
+ node.left.type === 'RestElement'
517
+ ) {
518
+ const value = /** @type {Expression} */ (context.visit(node.right));
519
+ const should_cache = value.type !== 'Identifier';
520
+ const rhs = should_cache ? b.id('$$value') : value;
521
+
522
+ let changed = false;
523
+
524
+ const assignments = extract_paths(node.left).map((path) => {
525
+ const value = path.expression?.(rhs);
526
+
527
+ let assignment = build_assignment('=', path.node, value, context);
528
+ if (assignment !== null) changed = true;
529
+
530
+ return (
531
+ assignment ??
532
+ b.assignment(
533
+ '=',
534
+ /** @type {Pattern} */ (context.visit(path.node)),
535
+ /** @type {Expression} */ (context.visit(value)),
536
+ )
537
+ );
538
+ });
539
+
540
+ if (!changed) {
541
+ // No change to output -> nothing to transform -> we can keep the original assignment
542
+ return null;
543
+ }
544
+
545
+ const is_standalone = /** @type {Node} */ (context.path.at(-1)).type.endsWith('Statement');
546
+ const sequence = b.sequence(assignments);
547
+
548
+ if (!is_standalone) {
549
+ // this is part of an expression, we need the sequence to end with the value
550
+ sequence.expressions.push(rhs);
551
+ }
552
+
553
+ if (should_cache) {
554
+ // the right hand side is a complex expression, wrap in an IIFE to cache it
555
+ const iife = b.arrow([rhs], sequence);
556
+
557
+ const iife_is_async =
558
+ is_expression_async(value) ||
559
+ assignments.some((assignment) => is_expression_async(assignment));
560
+
561
+ return iife_is_async ? b.await(b.call(b.async(iife), value)) : b.call(iife, value);
562
+ }
563
+
564
+ return sequence;
565
+ }
566
+
567
+ if (node.left.type !== 'Identifier' && node.left.type !== 'MemberExpression') {
568
+ throw new Error(`Unexpected assignment type ${node.left.type}`);
569
+ }
570
+
571
+ const transformed = build_assignment(node.operator, node.left, node.right, context);
572
+
573
+ if (transformed === node.left) {
574
+ return node;
575
+ }
576
+
577
+ return transformed;
579
578
  }
580
579
 
581
580
  export function build_assignment(operator, left, right, context) {
582
- let object = left;
583
-
584
- while (object.type === 'MemberExpression') {
585
- // @ts-expect-error
586
- object = object.object;
587
- }
588
-
589
- if (object.type !== 'Identifier') {
590
- return null;
591
- }
592
-
593
- const binding = context.state.scope.get(object.name);
594
- if (!binding) return null;
595
-
596
- const transform = binding.transform;
597
-
598
- // reassignment
599
- if (object === left || (left.type === 'MemberExpression' && left.computed && operator === '=')) {
600
- const assign_fn = transform?.assign || transform?.assign_tracked;
601
- if (assign_fn) {
602
- let value = /** @type {Expression} */ (
603
- context.visit(build_assignment_value(operator, left, right))
604
- );
605
-
606
- return assign_fn(
607
- object,
608
- value,
609
- left.type === 'MemberExpression' && left.computed
610
- ? context.visit(left.property)
611
- : undefined,
612
- );
613
- }
614
- }
615
-
616
- // mutation
617
- if (transform?.mutate) {
618
- return transform.mutate(
619
- object,
620
- b.assignment(
621
- operator,
622
- /** @type {Pattern} */ (context.visit(left)),
623
- /** @type {Expression} */ (context.visit(right)),
624
- ),
625
- );
626
- }
627
-
628
- return null;
581
+ let object = left;
582
+
583
+ while (object.type === 'MemberExpression') {
584
+ // @ts-expect-error
585
+ object = object.object;
586
+ }
587
+
588
+ if (object.type !== 'Identifier') {
589
+ return null;
590
+ }
591
+
592
+ const binding = context.state.scope.get(object.name);
593
+ if (!binding) return null;
594
+
595
+ const transform = binding.transform;
596
+
597
+ // reassignment
598
+ if (object === left || (left.type === 'MemberExpression' && left.computed && operator === '=')) {
599
+ const assign_fn = transform?.assign || transform?.assign_tracked;
600
+ if (assign_fn) {
601
+ let value = /** @type {Expression} */ (
602
+ context.visit(build_assignment_value(operator, left, right))
603
+ );
604
+
605
+ return assign_fn(
606
+ object,
607
+ value,
608
+ left.type === 'MemberExpression' && left.computed
609
+ ? context.visit(left.property)
610
+ : undefined,
611
+ );
612
+ }
613
+ }
614
+
615
+ // mutation
616
+ if (transform?.mutate) {
617
+ return transform.mutate(
618
+ object,
619
+ b.assignment(
620
+ operator,
621
+ /** @type {Pattern} */ (context.visit(left)),
622
+ /** @type {Expression} */ (context.visit(right)),
623
+ ),
624
+ );
625
+ }
626
+
627
+ return null;
629
628
  }
630
629
 
631
630
  const ATTR_REGEX = /[&"<]/g;
632
631
  const CONTENT_REGEX = /[&<]/g;
633
632
 
634
633
  export function escape_html(value, is_attr = false) {
635
- const str = String(value ?? '');
634
+ const str = String(value ?? '');
636
635
 
637
- const pattern = is_attr ? ATTR_REGEX : CONTENT_REGEX;
638
- pattern.lastIndex = 0;
636
+ const pattern = is_attr ? ATTR_REGEX : CONTENT_REGEX;
637
+ pattern.lastIndex = 0;
639
638
 
640
- let escaped = '';
641
- let last = 0;
639
+ let escaped = '';
640
+ let last = 0;
642
641
 
643
- while (pattern.test(str)) {
644
- const i = pattern.lastIndex - 1;
645
- const ch = str[i];
646
- escaped += str.substring(last, i) + (ch === '&' ? '&amp;' : ch === '"' ? '&quot;' : '&lt;');
647
- last = i + 1;
648
- }
642
+ while (pattern.test(str)) {
643
+ const i = pattern.lastIndex - 1;
644
+ const ch = str[i];
645
+ escaped += str.substring(last, i) + (ch === '&' ? '&amp;' : ch === '"' ? '&quot;' : '&lt;');
646
+ last = i + 1;
647
+ }
649
648
 
650
- return escaped + str.substring(last);
649
+ return escaped + str.substring(last);
651
650
  }
652
651
 
653
652
  export function hash(str) {
654
- str = str.replace(regex_return_characters, '');
655
- let hash = 5381;
656
- let i = str.length;
653
+ str = str.replace(regex_return_characters, '');
654
+ let hash = 5381;
655
+ let i = str.length;
657
656
 
658
- while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
659
- return (hash >>> 0).toString(36);
657
+ while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
658
+ return (hash >>> 0).toString(36);
659
+ }
660
+
661
+ const common_dom_names = new Set([
662
+ 'div',
663
+ 'span',
664
+ 'input',
665
+ 'textarea',
666
+ 'select',
667
+ 'button',
668
+ 'a',
669
+ 'p',
670
+ 'img',
671
+ 'form',
672
+ 'label',
673
+ 'ul',
674
+ 'ol',
675
+ 'li',
676
+ 'table',
677
+ 'thead',
678
+ 'tbody',
679
+ 'tr',
680
+ 'td',
681
+ 'th',
682
+ 'section',
683
+ 'header',
684
+ 'footer',
685
+ 'nav',
686
+ 'main',
687
+ 'article',
688
+ 'aside',
689
+ 'h1',
690
+ 'h2',
691
+ 'h3',
692
+ 'h4',
693
+ 'h5',
694
+ 'h6',
695
+ ]);
696
+
697
+ export function is_element_dom_element(node, context) {
698
+ if (node.id.type === 'Identifier' && node.id.name[0].toLowerCase() === node.id.name[0]) {
699
+ if (common_dom_names.has(node.id.name)) {
700
+ return true;
701
+ }
702
+ const binding = context.state.scope.get(node.id.name);
703
+ if (binding == null) {
704
+ return true;
705
+ }
706
+ }
707
+ return false;
660
708
  }