ripple 0.3.4 → 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 CHANGED
@@ -1,5 +1,23 @@
1
1
  # ripple
2
2
 
3
+ ## 0.3.5
4
+
5
+ ### Patch Changes
6
+
7
+ - [#827](https://github.com/Ripple-TS/ripple/pull/827)
8
+ [`218a72c`](https://github.com/Ripple-TS/ripple/commit/218a72c3e663910636eec1d065c58afe30813c84)
9
+ Thanks [@trueadm](https://github.com/trueadm)! - fix(compiler): handle
10
+ UpdateExpression on lazy bindings with default values
11
+
12
+ Update expressions (`++`/`--`) on lazy destructured bindings with default values
13
+ now work correctly. For postfix operations (`count++`), an IIFE captures the
14
+ fallback value before incrementing. Also added `fallback` function to server
15
+ runtime.
16
+
17
+ - Updated dependencies
18
+ [[`218a72c`](https://github.com/Ripple-TS/ripple/commit/218a72c3e663910636eec1d065c58afe30813c84)]:
19
+ - ripple@0.3.5
20
+
3
21
  ## 0.3.4
4
22
 
5
23
  ### Patch Changes
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Ripple is an elegant TypeScript UI framework",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.3.4",
6
+ "version": "0.3.5",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index-client.js",
9
9
  "main": "src/runtime/index-client.js",
@@ -105,6 +105,6 @@
105
105
  "vscode-languageserver-types": "^3.17.5"
106
106
  },
107
107
  "peerDependencies": {
108
- "ripple": "0.3.4"
108
+ "ripple": "0.3.5"
109
109
  }
110
110
  }
@@ -85,7 +85,8 @@ function setup_lazy_transforms(pattern, source_id, state, writable) {
85
85
  const binding = state.scope.get(name);
86
86
 
87
87
  if (binding !== null) {
88
- binding.kind = path.has_default_value ? 'lazy_fallback' : 'lazy';
88
+ const has_fallback = path.has_default_value;
89
+ binding.kind = has_fallback ? 'lazy_fallback' : 'lazy';
89
90
 
90
91
  binding.transform = {
91
92
  read: (_) => {
@@ -101,8 +102,40 @@ function setup_lazy_transforms(pattern, source_id, state, writable) {
101
102
  value,
102
103
  );
103
104
  };
104
- binding.transform.update = (node) =>
105
- b.update(node.operator, path.update_expression(source_id), node.prefix);
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
+ }
106
139
  }
107
140
  }
108
141
  }
@@ -2078,6 +2078,14 @@ const visitors = {
2078
2078
  }
2079
2079
  const argument = node.argument;
2080
2080
 
2081
+ // Handle lazy binding updates (e.g., a++ where a is from let &{a} = obj)
2082
+ if (argument.type === 'Identifier') {
2083
+ const binding = context.state.scope?.get(argument.name);
2084
+ if (binding?.transform?.update && binding.node !== argument) {
2085
+ return binding.transform.update(node);
2086
+ }
2087
+ }
2088
+
2081
2089
  if (
2082
2090
  argument.type === 'MemberExpression' &&
2083
2091
  (argument.tracked || (argument.property.type === 'Identifier' && argument.property.tracked))
@@ -1158,7 +1158,7 @@ export interface Binding {
1158
1158
  transform?: {
1159
1159
  read: (node?: AST.Identifier) => AST.Expression;
1160
1160
  assign?: (node: AST.Pattern, value: AST.Expression) => AST.AssignmentExpression;
1161
- update?: (node: AST.UpdateExpression) => AST.UpdateExpression;
1161
+ update?: (node: AST.UpdateExpression) => AST.Expression;
1162
1162
  };
1163
1163
  }
1164
1164
 
@@ -792,3 +792,14 @@ ripple_array.from_async = async function (arrayLike, map_fn, thisArg) {
792
792
  export function ripple_object(obj) {
793
793
  return obj;
794
794
  }
795
+
796
+ /**
797
+ * Returns the fallback value if the given value is undefined.
798
+ * @template T
799
+ * @param {T | undefined} value
800
+ * @param {T} fallback
801
+ * @returns {T}
802
+ */
803
+ export function fallback(value, fallback) {
804
+ return value === undefined ? fallback : value;
805
+ }
@@ -182,4 +182,28 @@ describe('lazy destructuring', () => {
182
182
  render(Test);
183
183
  expect(container.querySelector('pre')!.textContent).toBe('1-99');
184
184
  });
185
+
186
+ it('supports update expressions on lazy bindings with default values', () => {
187
+ component Test() {
188
+ const obj: { count?: number } = {};
189
+ let &{ count = 0 } = obj;
190
+ count++;
191
+ count++;
192
+ <pre>{obj.count}</pre>
193
+ }
194
+
195
+ render(Test);
196
+ expect(container.querySelector('pre')!.textContent).toBe('2');
197
+ });
198
+
199
+ it('supports member access on lazy destructured objects', () => {
200
+ component Test() {
201
+ const obj = { user: { name: 'Alice', age: 30 } };
202
+ const &{ user } = obj;
203
+ <pre>{`${user.name}-${user.age}`}</pre>
204
+ }
205
+
206
+ render(Test);
207
+ expect(container.querySelector('pre')!.textContent).toBe('Alice-30');
208
+ });
185
209
  });
@@ -0,0 +1,103 @@
1
+ describe('lazy destructuring', () => {
2
+ it('lazily accesses object properties', async () => {
3
+ component Inner(&{ a, b }: { a: number; b: string }) {
4
+ <pre>{`${a}-${b}`}</pre>
5
+ }
6
+
7
+ component Test() {
8
+ <Inner a={1} b="hello" />
9
+ }
10
+
11
+ const { body } = await render(Test);
12
+ expect(body).toBeHtml('<pre>1-hello</pre>');
13
+ });
14
+
15
+ it('supports default values in lazy object destructuring', async () => {
16
+ component Test() {
17
+ const obj = { a: 5 };
18
+ const &{ a, b = 99 } = obj;
19
+ <pre>{`${a}-${b}`}</pre>
20
+ }
21
+
22
+ const { body } = await render(Test);
23
+ expect(body).toBeHtml('<pre>5-99</pre>');
24
+ });
25
+
26
+ it('supports let lazy destructuring with assignment writeback', async () => {
27
+ component Test() {
28
+ const obj = { a: 1, b: 2 };
29
+ let &{ a, b } = obj;
30
+ a = 10;
31
+ b = 20;
32
+ <pre>{`${obj.a}-${obj.b}`}</pre>
33
+ }
34
+
35
+ const { body } = await render(Test);
36
+ expect(body).toBeHtml('<pre>10-20</pre>');
37
+ });
38
+
39
+ it('supports compound assignment operators on lazy bindings', async () => {
40
+ component Test() {
41
+ const obj = { a: 5, b: 10 };
42
+ let &{ a, b } = obj;
43
+ a += 3;
44
+ b *= 2;
45
+ <pre>{`${obj.a}-${obj.b}`}</pre>
46
+ }
47
+
48
+ const { body } = await render(Test);
49
+ expect(body).toBeHtml('<pre>8-20</pre>');
50
+ });
51
+
52
+ it('supports update expressions on lazy bindings', async () => {
53
+ component Test() {
54
+ const obj = { count: 0 };
55
+ let &{ count } = obj;
56
+ count++;
57
+ count++;
58
+ count--;
59
+ <pre>{obj.count}</pre>
60
+ }
61
+
62
+ const { body } = await render(Test);
63
+ expect(body).toBeHtml('<pre>1</pre>');
64
+ });
65
+
66
+ it('supports update expressions on lazy bindings with default values', async () => {
67
+ component Test() {
68
+ const obj: { count?: number } = {};
69
+ let &{ count = 0 } = obj;
70
+ count++;
71
+ count++;
72
+ <pre>{obj.count}</pre>
73
+ }
74
+
75
+ const { body } = await render(Test);
76
+ expect(body).toBeHtml('<pre>2</pre>');
77
+ });
78
+
79
+ it('supports function params with lazy destructuring and default values', async () => {
80
+ component Test() {
81
+ function calc(&{ x, y = 100 }: { x: number; y?: number }) {
82
+ return x + y;
83
+ }
84
+ const a = calc({ x: 5, y: 10 });
85
+ const b = calc({ x: 5 });
86
+ <pre>{`${a}-${b}`}</pre>
87
+ }
88
+
89
+ const { body } = await render(Test);
90
+ expect(body).toBeHtml('<pre>15-105</pre>');
91
+ });
92
+
93
+ it('supports member access on lazy destructured objects', async () => {
94
+ component Test() {
95
+ const obj = { user: { name: 'Alice', age: 30 } };
96
+ const &{ user } = obj;
97
+ <pre>{`${user.name}-${user.age}`}</pre>
98
+ }
99
+
100
+ const { body } = await render(Test);
101
+ expect(body).toBeHtml('<pre>Alice-30</pre>');
102
+ });
103
+ });