ripple 0.1.1 → 0.2.0

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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +56 -24
  3. package/src/ai.js +292 -0
  4. package/src/compiler/errors.js +26 -0
  5. package/src/compiler/index.js +26 -0
  6. package/src/compiler/phases/1-parse/index.js +543 -0
  7. package/src/compiler/phases/1-parse/style.js +566 -0
  8. package/src/compiler/phases/2-analyze/index.js +509 -0
  9. package/src/compiler/phases/2-analyze/prune.js +572 -0
  10. package/src/compiler/phases/3-transform/index.js +1572 -0
  11. package/src/compiler/phases/3-transform/segments.js +91 -0
  12. package/src/compiler/phases/3-transform/stylesheet.js +372 -0
  13. package/src/compiler/scope.js +421 -0
  14. package/src/compiler/utils.js +552 -0
  15. package/src/constants.js +4 -0
  16. package/src/jsx-runtime.d.ts +94 -0
  17. package/src/jsx-runtime.js +46 -0
  18. package/src/runtime/array.js +215 -0
  19. package/src/runtime/index.js +39 -0
  20. package/src/runtime/internal/client/blocks.js +247 -0
  21. package/src/runtime/internal/client/constants.js +23 -0
  22. package/src/runtime/internal/client/events.js +223 -0
  23. package/src/runtime/internal/client/for.js +388 -0
  24. package/src/runtime/internal/client/if.js +35 -0
  25. package/src/runtime/internal/client/index.js +53 -0
  26. package/src/runtime/internal/client/operations.js +72 -0
  27. package/src/runtime/internal/client/portal.js +33 -0
  28. package/src/runtime/internal/client/render.js +156 -0
  29. package/src/runtime/internal/client/runtime.js +909 -0
  30. package/src/runtime/internal/client/template.js +51 -0
  31. package/src/runtime/internal/client/try.js +139 -0
  32. package/src/runtime/internal/client/utils.js +16 -0
  33. package/src/utils/ast.js +214 -0
  34. package/src/utils/builders.js +733 -0
  35. package/src/utils/patterns.js +23 -0
  36. package/src/utils/sanitize_template_string.js +7 -0
  37. package/test-mappings.js +0 -0
  38. package/types/index.d.ts +2 -0
  39. package/.npmignore +0 -2
  40. package/History.md +0 -3
  41. package/Readme.md +0 -151
  42. package/lib/exec/index.js +0 -60
  43. package/ripple.js +0 -645
@@ -0,0 +1,509 @@
1
+ import * as b from '../../../utils/builders.js';
2
+ import { walk } from 'zimmerframe';
3
+ import { create_scopes, ScopeRoot } from '../../scope.js';
4
+ import {
5
+ get_delegated_event,
6
+ is_event_attribute,
7
+ is_inside_component,
8
+ is_svelte_import,
9
+ is_tracked_name
10
+ } from '../../utils.js';
11
+ import { extract_paths } from '../../../utils/ast.js';
12
+ import is_reference from 'is-reference';
13
+ import { prune_css } from './prune.js';
14
+ import { error } from '../../errors.js';
15
+
16
+ function visit_function(node, context) {
17
+ node.metadata = {
18
+ hoisted: false,
19
+ hoisted_params: [],
20
+ scope: context.state.scope,
21
+ tracked: false
22
+ };
23
+
24
+ if (node.params.length > 0) {
25
+ for (let i = 0; i < node.params.length; i += 1) {
26
+ const param = node.params[i];
27
+ if (param.type === 'ObjectPattern') {
28
+ const paths = extract_paths(param);
29
+
30
+ for (const path of paths) {
31
+ const name = path.node.name;
32
+ const binding = context.state.scope.get(name);
33
+
34
+ if (binding !== null && is_tracked_name(name)) {
35
+ const id = context.state.scope.generate('arg');
36
+ node.params[i] = b.id(id);
37
+ binding.kind = 'prop';
38
+
39
+ binding.transform = {
40
+ read: (_) => b.call('$.get_property', b.id(id), b.literal(name))
41
+ };
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
47
+
48
+ context.next({
49
+ ...context.state,
50
+ function_depth: context.state.function_depth + 1,
51
+ expression: null
52
+ });
53
+ }
54
+
55
+ function mark_as_tracked(path) {
56
+ for (let i = 0; i < path.length; i += 1) {
57
+ const node = path[i];
58
+
59
+ if (node.type === 'Component') {
60
+ break;
61
+ }
62
+ if (
63
+ node.type === 'FunctionExpression' ||
64
+ node.type === 'ArrowFunctionExpression' ||
65
+ node.type === 'FunctionDeclaration'
66
+ ) {
67
+ node.metadata.tracked = true;
68
+ break;
69
+ }
70
+ }
71
+ }
72
+
73
+ const visitors = {
74
+ _(node, { state, next }) {
75
+ const scope = state.scopes.get(node);
76
+ next(scope !== undefined && scope !== state.scope ? { ...state, scope } : state);
77
+ },
78
+
79
+ Identifier(node, context) {
80
+ const binding = context.state.scope.get(node.name);
81
+ const parent = context.path.at(-1);
82
+
83
+ if (
84
+ is_reference(node, /** @type {Node} */ (parent)) &&
85
+ context.state.metadata?.tracking === false &&
86
+ is_tracked_name(node.name) &&
87
+ binding?.node !== node
88
+ ) {
89
+ context.state.metadata.tracking = true;
90
+ }
91
+
92
+ context.next();
93
+ },
94
+
95
+ MemberExpression(node, context) {
96
+ const parent = context.path.at(-1);
97
+
98
+ if (
99
+ context.state.metadata?.tracking === false &&
100
+ node.property.type === 'Identifier' &&
101
+ !node.computed &&
102
+ is_tracked_name(node.property.name) &&
103
+ parent.type !== 'AssignmentExpression'
104
+ ) {
105
+ context.state.metadata.tracking = true;
106
+ }
107
+ context.next();
108
+ },
109
+
110
+ CallExpression(node, context) {
111
+ if (context.state.metadata?.tracking === false) {
112
+ context.state.metadata.tracking = true;
113
+ }
114
+
115
+ context.next();
116
+ },
117
+
118
+ ObjectExpression(node, context) {
119
+ for (const property of node.properties) {
120
+ if (
121
+ property.type === 'Property' &&
122
+ !property.computed &&
123
+ property.key.type === 'Identifier' &&
124
+ property.kind === 'init' &&
125
+ is_tracked_name(property.key.name)
126
+ ) {
127
+ mark_as_tracked(context.path);
128
+ }
129
+ }
130
+
131
+ context.next();
132
+ },
133
+
134
+ ArrayExpression(node, context) {
135
+ for (const element of node.elements) {
136
+ if (element !== null && element.type === 'Identifier' && is_tracked_name(element.name)) {
137
+ mark_as_tracked(context.path);
138
+ }
139
+ }
140
+
141
+ context.next();
142
+ },
143
+
144
+ VariableDeclaration(node, context) {
145
+ const { state, visit, path } = context;
146
+
147
+ for (const declarator of node.declarations) {
148
+ const metadata = { tracking: false, await: false };
149
+ const parent = path.at(-1);
150
+ const init_is_untracked =
151
+ declarator.init !== null &&
152
+ declarator.init.type === 'CallExpression' &&
153
+ is_svelte_import(declarator.init.callee, context) &&
154
+ declarator.init.callee.type === 'Identifier' &&
155
+ (declarator.init.callee.name === 'untrack' || declarator.init.callee.name === 'deferred');
156
+
157
+ if (declarator.id.type === 'Identifier') {
158
+ const binding = state.scope.get(declarator.id.name);
159
+
160
+ if (
161
+ binding !== null &&
162
+ is_tracked_name(declarator.id.name) &&
163
+ parent?.type !== 'ForOfStatement'
164
+ ) {
165
+ binding.kind = 'tracked';
166
+
167
+ mark_as_tracked(path);
168
+
169
+ visit(declarator, { ...state, metadata });
170
+
171
+ if (init_is_untracked && metadata.tracking) {
172
+ metadata.tracking = false;
173
+ }
174
+
175
+ binding.transform = {
176
+ read: (node) => {
177
+ return metadata.tracking && !metadata.await
178
+ ? b.call('$.get_computed', node)
179
+ : b.call('$.get_tracked', node);
180
+ },
181
+ assign: (node, value) => {
182
+ return b.call('$.set', node, value, b.id('__block'));
183
+ },
184
+ update: (node) => {
185
+ return b.call(
186
+ node.prefix ? '$.update_pre' : '$.update',
187
+ node.argument,
188
+ b.id('__block'),
189
+ node.operator === '--' && b.literal(-1)
190
+ );
191
+ }
192
+ };
193
+ } else {
194
+ visit(declarator, state);
195
+ }
196
+ } else {
197
+ const paths = extract_paths(declarator.id);
198
+ const has_tracked = paths.some(
199
+ (path) => path.node.type === 'Identifier' && is_tracked_name(path.node.name)
200
+ );
201
+
202
+ if (has_tracked) {
203
+ const tmp = state.scope.generate('tmp');
204
+ declarator.transformed = b.id(tmp);
205
+
206
+ if (declarator.init !== null) {
207
+ visit(declarator.init, { ...state, metadata });
208
+ }
209
+
210
+ if (init_is_untracked && metadata.tracking) {
211
+ metadata.tracking = false;
212
+ }
213
+
214
+ for (const path of paths) {
215
+ const binding = state.scope.get(path.node.name);
216
+
217
+ binding.transform = {
218
+ read: (node) => {
219
+ const value = path.expression?.(b.id(tmp));
220
+
221
+ if (metadata.tracking && metadata.await) {
222
+ // TODO
223
+ debugger;
224
+ } else if (metadata.tracking && !metadata.await) {
225
+ if (is_tracked_name(path.node.name) && value.type === 'MemberExpression') {
226
+ return b.call(
227
+ '$.get_property',
228
+ b.call('$.get_computed', value.object),
229
+ value.property.type === 'Identifier'
230
+ ? b.literal(value.property.name)
231
+ : value.property
232
+ );
233
+ }
234
+
235
+ const key =
236
+ value.property.type === 'Identifier'
237
+ ? b.key(value.property.name)
238
+ : value.property;
239
+
240
+ return b.member(
241
+ b.call('$.get_computed', value.object),
242
+ key,
243
+ key.type === 'Literal'
244
+ );
245
+ }
246
+
247
+ if (is_tracked_name(path.node.name) && value.type === 'MemberExpression') {
248
+ return b.call(
249
+ '$.get_property',
250
+ value.object,
251
+ value.property.type === 'Identifier'
252
+ ? b.literal(value.property.name)
253
+ : value.property
254
+ );
255
+ }
256
+
257
+ return value;
258
+ }
259
+ };
260
+ }
261
+ } else {
262
+ visit(declarator, state);
263
+ }
264
+ }
265
+
266
+ declarator.metadata = metadata;
267
+ }
268
+ },
269
+
270
+ ArrowFunctionExpression(node, context) {
271
+ visit_function(node, context);
272
+ },
273
+ FunctionExpression(node, context) {
274
+ visit_function(node, context);
275
+ },
276
+ FunctionDeclaration(node, context) {
277
+ visit_function(node, context);
278
+ },
279
+
280
+ Component(node, context) {
281
+ context.state.component = node;
282
+
283
+ if (node.params.length > 0) {
284
+ const props = node.params[0];
285
+
286
+ if (props.type === 'ObjectPattern') {
287
+ const paths = extract_paths(props);
288
+
289
+ for (const path of paths) {
290
+ const name = path.node.name;
291
+ const binding = context.state.scope.get(name);
292
+
293
+ if (binding !== null && is_tracked_name(name)) {
294
+ binding.kind = 'prop';
295
+
296
+ binding.transform = {
297
+ read: (_) => b.call('$.get_property', b.id('__props'), b.literal(name))
298
+ };
299
+ }
300
+ }
301
+ }
302
+ }
303
+ const elements = [];
304
+
305
+ context.next({ ...context.state, elements });
306
+
307
+ const css = node.css;
308
+
309
+ if (css !== null) {
310
+ for (const node of elements) {
311
+ prune_css(css, node);
312
+ }
313
+ }
314
+ },
315
+
316
+ ForStatement(node, context) {
317
+ if (is_inside_component(context)) {
318
+ error(
319
+ 'For loops are not supported in components. Use for...of instead.',
320
+ context.state.analysis.module.filename,
321
+ node
322
+ );
323
+ }
324
+
325
+ context.next();
326
+ },
327
+
328
+ ForInStatement(node, context) {
329
+ if (is_inside_component(context)) {
330
+ error(
331
+ 'For...in loops are not supported in components. Use for...of instead.',
332
+ context.state.analysis.module.filename,
333
+ node
334
+ );
335
+ }
336
+
337
+ context.next();
338
+ },
339
+
340
+ JSXElement(_, context) {
341
+ {
342
+ error(
343
+ 'Elements cannot be used as generic expressions, only as statements within a component',
344
+ context.state.analysis.module.filename,
345
+ node
346
+ );
347
+ }
348
+ },
349
+
350
+ Element(node, { state, visit }) {
351
+ const type = node.id.name;
352
+ const is_dom_element = type[0].toLowerCase() === type[0];
353
+ const attribute_names = new Set();
354
+
355
+ if (is_dom_element) {
356
+ if (state.elements) {
357
+ state.elements.push(node);
358
+ }
359
+
360
+ for (const attr of node.attributes) {
361
+ if (attr.type === 'Attribute') {
362
+ if (attr.name.type === 'Identifier') {
363
+ attribute_names.add(attr.name);
364
+
365
+ if (is_event_attribute(attr.name.name)) {
366
+ const event_name = attr.name.name.slice(2).toLowerCase();
367
+ const handler = visit(attr.value, state);
368
+ const delegated_event = get_delegated_event(event_name, handler, state);
369
+
370
+ if (delegated_event !== null) {
371
+ if (delegated_event.hoisted) {
372
+ delegated_event.function.metadata.hoisted = true;
373
+ delegated_event.hoisted = true;
374
+ }
375
+
376
+ if (attr.metadata === undefined) {
377
+ attr.metadata = {};
378
+ }
379
+
380
+ attr.metadata.delegated = delegated_event;
381
+ }
382
+ }
383
+ }
384
+ }
385
+ }
386
+ } else {
387
+ for (const attr of node.attributes) {
388
+ if (attr.type === 'Attribute') {
389
+ if (attr.name.type === 'Identifier') {
390
+ attribute_names.add(attr.name);
391
+ }
392
+ }
393
+ }
394
+
395
+ let implicit_children = false;
396
+ let explicit_children = false;
397
+
398
+ for (const child of node.children) {
399
+ if (child.type === 'Fragment') {
400
+ if (child.id.name === '$children') {
401
+ explicit_children = true;
402
+ if (implicit_children) {
403
+ error(
404
+ 'Cannot have both implicit and explicit children',
405
+ context.state.analysis.module.filename,
406
+ node
407
+ );
408
+ }
409
+ }
410
+ } else if (child.type !== 'EmptyStatement') {
411
+ implicit_children = true;
412
+ if (explicit_children) {
413
+ error(
414
+ 'Cannot have both implicit and explicit children',
415
+ context.state.analysis.module.filename,
416
+ node
417
+ );
418
+ }
419
+ }
420
+ }
421
+ }
422
+
423
+ // Validation
424
+ for (const attribute of attribute_names) {
425
+ const name = attribute.name;
426
+ if (name === 'children') {
427
+ if (is_dom_element) {
428
+ error(
429
+ 'Cannot have a `children` prop on an element',
430
+ state.analysis.module.filename,
431
+ attribute
432
+ );
433
+ } else {
434
+ error(
435
+ 'Cannot have a `children` prop on a component, did you mean `$children`?',
436
+ state.analysis.module.filename,
437
+ attribute
438
+ );
439
+ }
440
+ } else if (name === 'ref') {
441
+ if (is_dom_element) {
442
+ error(
443
+ 'Cannot have a `ref` prop on an element, did you mean `$ref`?',
444
+ state.analysis.module.filename,
445
+ attribute
446
+ );
447
+ } else {
448
+ error(
449
+ 'Cannot have a `ref` prop on a component, did you mean `$ref`?',
450
+ state.analysis.module.filename,
451
+ attribute
452
+ );
453
+ }
454
+ }
455
+
456
+ if (is_tracked_name(name)) {
457
+ attribute_names.forEach((n) => {
458
+ if (n.name.slice(1) === name) {
459
+ error(
460
+ `Cannot have both ${name} and ${name.slice(1)} on the same element`,
461
+ state.analysis.module.filename,
462
+ n
463
+ );
464
+ }
465
+ });
466
+ }
467
+ }
468
+
469
+ return {
470
+ ...node,
471
+ children: node.children.map((child) => visit(child))
472
+ };
473
+ },
474
+
475
+ AwaitExpression(node, context) {
476
+ if (is_inside_component(context)) {
477
+ if (context.state.metadata?.await === false) {
478
+ context.state.metadata.await = true;
479
+ }
480
+ }
481
+
482
+ context.next();
483
+ }
484
+ };
485
+
486
+ export function analyze(ast, filename) {
487
+ const scope_root = new ScopeRoot();
488
+
489
+ const { scope, scopes } = create_scopes(ast, scope_root, null);
490
+
491
+ const analysis = {
492
+ module: { ast, scope, scopes, filename },
493
+ ast,
494
+ scope,
495
+ scopes
496
+ };
497
+
498
+ walk(
499
+ ast,
500
+ {
501
+ scope,
502
+ scopes,
503
+ analysis
504
+ },
505
+ visitors
506
+ );
507
+
508
+ return analysis;
509
+ }