ripple 0.2.111 → 0.2.112
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/package.json +2 -1
- package/src/compiler/phases/1-parse/index.js +5 -1
- package/src/compiler/phases/3-transform/client/index.js +4 -0
- package/src/compiler/phases/3-transform/segments.js +9 -9
- package/src/compiler/phases/3-transform/server/index.js +19 -0
- package/src/runtime/internal/client/rpc.js +26 -11
- package/src/runtime/internal/server/index.js +1 -1
- package/src/runtime/internal/server/rpc.js +13 -0
- package/src/server/index.js +1 -0
- package/tests/client/__snapshots__/compiler.test.ripple.snap +8 -0
- package/tests/client/compiler.test.ripple +28 -0
- package/tests/client/composite.test.ripple +81 -82
- package/tests/server/__snapshots__/compiler.test.ripple.snap +8 -0
- package/tests/server/compiler.test.ripple +11 -0
- package/tests/server/composite.test.ripple +213 -0
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.2.
|
|
6
|
+
"version": "0.2.112",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "src/runtime/index-client.js",
|
|
9
9
|
"main": "src/runtime/index-client.js",
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"@sveltejs/acorn-typescript": "^1.0.6",
|
|
68
68
|
"acorn": "^8.15.0",
|
|
69
69
|
"clsx": "^2.1.1",
|
|
70
|
+
"devalue": "^5.3.2",
|
|
70
71
|
"esm-env": "^1.2.2",
|
|
71
72
|
"esrap": "^2.1.0",
|
|
72
73
|
"is-reference": "^3.0.3",
|
|
@@ -106,7 +106,11 @@ function RipplePlugin(config) {
|
|
|
106
106
|
if (inComponent) {
|
|
107
107
|
// Inside nested functions (scopeStack.length >= 5), treat < as relational/generic operator
|
|
108
108
|
// At component top-level (scopeStack.length <= 4), apply JSX detection logic
|
|
109
|
-
if
|
|
109
|
+
// BUT: if the < is followed by /, it's a closing JSX tag, not a less-than operator
|
|
110
|
+
const nextChar = this.pos + 1 < this.input.length ? this.input.charCodeAt(this.pos + 1) : -1;
|
|
111
|
+
const isClosingTag = nextChar === 47; // '/'
|
|
112
|
+
|
|
113
|
+
if (this.scopeStack.length >= 5 && !isClosingTag) {
|
|
110
114
|
// Inside function - treat as TypeScript generic, not JSX
|
|
111
115
|
++this.pos;
|
|
112
116
|
return this.finishToken(tt.relational, '<');
|
|
@@ -59,6 +59,10 @@ function visit_function(node, context) {
|
|
|
59
59
|
|
|
60
60
|
for (const param of node.params) {
|
|
61
61
|
delete param.typeAnnotation;
|
|
62
|
+
// Handle AssignmentPattern (parameters with default values)
|
|
63
|
+
if (param.type === 'AssignmentPattern' && param.left) {
|
|
64
|
+
delete param.left.typeAnnotation;
|
|
65
|
+
}
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
if (metadata?.hoisted === true) {
|
|
@@ -55,7 +55,7 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
|
|
|
55
55
|
continue; // Not a whole word match, keep searching
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
|
|
59
59
|
sourceIndex = i + text.length;
|
|
60
60
|
return i;
|
|
61
61
|
}
|
|
@@ -87,7 +87,7 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
|
|
|
87
87
|
continue; // Not a whole word match, keep searching
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
-
|
|
90
|
+
|
|
91
91
|
generatedIndex = i + text.length;
|
|
92
92
|
return i;
|
|
93
93
|
}
|
|
@@ -98,7 +98,7 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
|
|
|
98
98
|
// Collect text tokens from AST nodes
|
|
99
99
|
/** @type {string[]} */
|
|
100
100
|
const tokens = [];
|
|
101
|
-
|
|
101
|
+
|
|
102
102
|
// We have to visit everything in generated order to maintain correct indices
|
|
103
103
|
walk(ast, null, {
|
|
104
104
|
_(node, { visit }) {
|
|
@@ -194,24 +194,24 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
|
|
|
194
194
|
return;
|
|
195
195
|
} else if (node.type === 'JSXElement') {
|
|
196
196
|
// Manually visit in source order: opening element, children, closing element
|
|
197
|
-
|
|
197
|
+
|
|
198
198
|
// 1. Visit opening element (name and attributes)
|
|
199
199
|
if (node.openingElement) {
|
|
200
200
|
visit(node.openingElement);
|
|
201
201
|
}
|
|
202
|
-
|
|
202
|
+
|
|
203
203
|
// 2. Visit children in order
|
|
204
204
|
if (node.children) {
|
|
205
205
|
for (const child of node.children) {
|
|
206
206
|
visit(child);
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
|
-
|
|
209
|
+
|
|
210
210
|
// 3. Push closing tag name (not visited by AST walker)
|
|
211
211
|
if (!node.openingElement?.selfClosing && node.closingElement?.name?.type === 'JSXIdentifier') {
|
|
212
212
|
tokens.push(node.closingElement.name.name);
|
|
213
213
|
}
|
|
214
|
-
|
|
214
|
+
|
|
215
215
|
return;
|
|
216
216
|
} else if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {
|
|
217
217
|
// Visit in source order: id, params, body
|
|
@@ -991,7 +991,7 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
|
|
|
991
991
|
for (const text of tokens) {
|
|
992
992
|
const sourcePos = findInSource(text);
|
|
993
993
|
const genPos = findInGenerated(text);
|
|
994
|
-
|
|
994
|
+
|
|
995
995
|
if (sourcePos !== null && genPos !== null) {
|
|
996
996
|
mappings.push({
|
|
997
997
|
sourceOffsets: [sourcePos],
|
|
@@ -1009,4 +1009,4 @@ export function convert_source_map_to_mappings(ast, source, generated_code) {
|
|
|
1009
1009
|
code: generated_code,
|
|
1010
1010
|
mappings,
|
|
1011
1011
|
};
|
|
1012
|
-
}
|
|
1012
|
+
}
|
|
@@ -130,6 +130,13 @@ const visitors = {
|
|
|
130
130
|
return context.next();
|
|
131
131
|
},
|
|
132
132
|
|
|
133
|
+
NewExpression(node, context) {
|
|
134
|
+
if (!context.state.to_ts) {
|
|
135
|
+
delete node.typeArguments;
|
|
136
|
+
}
|
|
137
|
+
return context.next();
|
|
138
|
+
},
|
|
139
|
+
|
|
133
140
|
PropertyDefinition(node, context) {
|
|
134
141
|
if (!context.state.to_ts) {
|
|
135
142
|
delete node.typeAnnotation;
|
|
@@ -143,6 +150,10 @@ const visitors = {
|
|
|
143
150
|
delete node.typeParameters;
|
|
144
151
|
for (const param of node.params) {
|
|
145
152
|
delete param.typeAnnotation;
|
|
153
|
+
// Handle AssignmentPattern (parameters with default values)
|
|
154
|
+
if (param.type === 'AssignmentPattern' && param.left) {
|
|
155
|
+
delete param.left.typeAnnotation;
|
|
156
|
+
}
|
|
146
157
|
}
|
|
147
158
|
}
|
|
148
159
|
return context.next();
|
|
@@ -154,6 +165,10 @@ const visitors = {
|
|
|
154
165
|
delete node.typeParameters;
|
|
155
166
|
for (const param of node.params) {
|
|
156
167
|
delete param.typeAnnotation;
|
|
168
|
+
// Handle AssignmentPattern (parameters with default values)
|
|
169
|
+
if (param.type === 'AssignmentPattern' && param.left) {
|
|
170
|
+
delete param.left.typeAnnotation;
|
|
171
|
+
}
|
|
157
172
|
}
|
|
158
173
|
}
|
|
159
174
|
return context.next();
|
|
@@ -165,6 +180,10 @@ const visitors = {
|
|
|
165
180
|
delete node.typeParameters;
|
|
166
181
|
for (const param of node.params) {
|
|
167
182
|
delete param.typeAnnotation;
|
|
183
|
+
// Handle AssignmentPattern (parameters with default values)
|
|
184
|
+
if (param.type === 'AssignmentPattern' && param.left) {
|
|
185
|
+
delete param.left.typeAnnotation;
|
|
186
|
+
}
|
|
168
187
|
}
|
|
169
188
|
}
|
|
170
189
|
return context.next();
|
|
@@ -1,14 +1,29 @@
|
|
|
1
|
+
import * as devalue from 'devalue';
|
|
1
2
|
|
|
2
3
|
/**
|
|
3
|
-
* @param {string} hash
|
|
4
|
-
* @param {any[]} args
|
|
4
|
+
* @param {string} hash
|
|
5
|
+
* @param {any[]} args
|
|
5
6
|
*/
|
|
6
|
-
export function rpc(hash, args) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
export async function rpc(hash, args) {
|
|
8
|
+
const body = devalue.stringify(args);
|
|
9
|
+
let data;
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const response = await fetch('/_$_ripple_rpc_$_/' + hash, {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
headers: {
|
|
15
|
+
'Content-Type': 'application/json',
|
|
16
|
+
},
|
|
17
|
+
body,
|
|
18
|
+
});
|
|
19
|
+
data = await response.text();
|
|
20
|
+
} catch (err) {
|
|
21
|
+
throw new Error('An error occurred while trying to call the server function.');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (data === '') {
|
|
25
|
+
throw new Error('The server function end-point did not return a response. Are you running a Ripple server?');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return devalue.parse(data).value;
|
|
29
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { DERIVED, UNINITIALIZED } from '../client/constants.js';
|
|
3
3
|
import { is_tracked_object } from '../client/utils.js';
|
|
4
4
|
import { escape } from '../../../utils/escaping.js';
|
|
5
|
-
import { is_boolean_attribute } from '../../../compiler/utils';
|
|
5
|
+
import { is_boolean_attribute } from '../../../compiler/utils.js';
|
|
6
6
|
import { clsx } from 'clsx';
|
|
7
7
|
|
|
8
8
|
export { escape };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as devalue from 'devalue';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @template {any[]} T
|
|
5
|
+
* @template V
|
|
6
|
+
* @param {(...args: T) => Promise<V>} fn
|
|
7
|
+
* @param {string} rpc_arguments_string
|
|
8
|
+
*/
|
|
9
|
+
export async function executeServerFunction(fn, rpc_arguments_string) {
|
|
10
|
+
const rpc_arguments = devalue.parse(rpc_arguments_string);
|
|
11
|
+
const result = await fn.apply(null, rpc_arguments);
|
|
12
|
+
return devalue.stringify({ value: result });
|
|
13
|
+
}
|
package/src/server/index.js
CHANGED
|
@@ -23,3 +23,11 @@ exports[`compiler success tests > compiles tracked values in effect with update
|
|
|
23
23
|
state.postDecrement = _$_.update(count, __block, -1);
|
|
24
24
|
}));"
|
|
25
25
|
`;
|
|
26
|
+
|
|
27
|
+
exports[`compiler success tests > removes type assertions from function parameters and leaves default values 1`] = `
|
|
28
|
+
"import * as _$_ from 'ripple/internal/client';
|
|
29
|
+
|
|
30
|
+
function getString(e = 'test') {
|
|
31
|
+
return e;
|
|
32
|
+
}"
|
|
33
|
+
`;
|
|
@@ -510,4 +510,32 @@ const errorMap = new ErrorMap();`;
|
|
|
510
510
|
|
|
511
511
|
expect(result.js.code).toMatchSnapshot();
|
|
512
512
|
});
|
|
513
|
+
|
|
514
|
+
it('removes type assertions from function parameters and leaves default values', () => {
|
|
515
|
+
const source = `
|
|
516
|
+
function getString(e: string = 'test') {
|
|
517
|
+
return e;
|
|
518
|
+
}`;
|
|
519
|
+
|
|
520
|
+
const result = compile(source, 'test.ripple', { mode: 'client' });
|
|
521
|
+
|
|
522
|
+
expect(result.js.code).toMatchSnapshot();
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('compiles without needing semicolons between statements and JSX', () => {
|
|
526
|
+
const source = `export component App() {
|
|
527
|
+
<div>const code4 = 4
|
|
528
|
+
|
|
529
|
+
const code3 = 3
|
|
530
|
+
<div>
|
|
531
|
+
<div>
|
|
532
|
+
const code = 1
|
|
533
|
+
</div>
|
|
534
|
+
const code2 = 2
|
|
535
|
+
</div>
|
|
536
|
+
</div>
|
|
537
|
+
}`;
|
|
538
|
+
|
|
539
|
+
const result = compile(source, 'test.ripple', { mode: 'client' });
|
|
540
|
+
});
|
|
513
541
|
});
|
|
@@ -341,7 +341,7 @@ describe('composite components', () => {
|
|
|
341
341
|
component App() {
|
|
342
342
|
// Ambiguous generics vs JSX / less-than parsing scenarios
|
|
343
343
|
|
|
344
|
-
//
|
|
344
|
+
// 7. Generic following optional chaining
|
|
345
345
|
const maybe = {
|
|
346
346
|
factory<T>() {
|
|
347
347
|
return {
|
|
@@ -353,11 +353,11 @@ describe('composite components', () => {
|
|
|
353
353
|
};
|
|
354
354
|
const g = maybe?.factory<number>()?.make<boolean>();
|
|
355
355
|
|
|
356
|
-
//
|
|
356
|
+
// 8. Comparison operator (ensure '<' here NOT misparsed as generics)
|
|
357
357
|
let x = 10, y = 20;
|
|
358
358
|
const h = x < y ? 'lt' : 'ge';
|
|
359
359
|
|
|
360
|
-
//
|
|
360
|
+
// 9. Chained comparisons with intervening generics
|
|
361
361
|
class Box<T> {
|
|
362
362
|
value: T;
|
|
363
363
|
constructor(value?: T) {
|
|
@@ -375,11 +375,11 @@ describe('composite components', () => {
|
|
|
375
375
|
<span>{'Test'}</span>
|
|
376
376
|
</div>
|
|
377
377
|
|
|
378
|
-
//
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
378
|
+
// 11. Generic function call vs Element: Identifier followed by generic args
|
|
379
|
+
function identity<T>(value: T): T {
|
|
380
|
+
return value;
|
|
381
|
+
}
|
|
382
|
+
const j = identity<number>(42);
|
|
383
383
|
|
|
384
384
|
// 12. Member + generic call immediately followed by another call
|
|
385
385
|
class Factory {
|
|
@@ -390,25 +390,25 @@ describe('composite components', () => {
|
|
|
390
390
|
const factory = new Factory();
|
|
391
391
|
const k = factory.create<number>()(123);
|
|
392
392
|
|
|
393
|
-
//
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
393
|
+
// 13. Multiple generic segments in chain
|
|
394
|
+
function foo<T>() {
|
|
395
|
+
return {
|
|
396
|
+
bar<U>() {
|
|
397
|
+
return {
|
|
398
|
+
baz<V>() {
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
const l = foo<number>().bar<string>().baz<boolean>();
|
|
406
406
|
|
|
407
407
|
// 14. Generic with constraint + default
|
|
408
408
|
type Extractor<T extends { id: number } = { id: number }> = (v: T) => number;
|
|
409
409
|
const m: Extractor = (v) => v.id;
|
|
410
410
|
|
|
411
|
-
//
|
|
411
|
+
// 15. Generic in angle after "new" + trailing call
|
|
412
412
|
class Wrapper<T> {
|
|
413
413
|
value: T;
|
|
414
414
|
constructor() {
|
|
@@ -420,21 +420,21 @@ describe('composite components', () => {
|
|
|
420
420
|
}
|
|
421
421
|
const n = new Wrapper<number>().unwrap<string>();
|
|
422
422
|
|
|
423
|
-
//
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
//
|
|
423
|
+
// 16. Angle brackets inside type assertion vs generic call
|
|
424
|
+
function getUnknown(): unknown {
|
|
425
|
+
return new Map<string, number>([['a', 1]]);
|
|
426
|
+
}
|
|
427
|
+
getUnknown.factory = function<T>() {
|
|
428
|
+
return {
|
|
429
|
+
make<U>() {
|
|
430
|
+
return 2;
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
};
|
|
434
|
+
const raw = getUnknown();
|
|
435
|
+
const o = (raw as Map<string, number>).get('a');
|
|
436
|
+
|
|
437
|
+
// 17. Generic with comma + trailing less-than comparison on next token
|
|
438
438
|
class Pair<T1, T2> {
|
|
439
439
|
first: T1;
|
|
440
440
|
second: T2;
|
|
@@ -446,7 +446,7 @@ describe('composite components', () => {
|
|
|
446
446
|
const p = new Pair<number, string>();
|
|
447
447
|
const q = 1 < 2 ? p : null;
|
|
448
448
|
|
|
449
|
-
//
|
|
449
|
+
// 18. Nested generics with line breaks resembling JSX indentation
|
|
450
450
|
interface Node<T> {
|
|
451
451
|
value: T;
|
|
452
452
|
}
|
|
@@ -466,7 +466,7 @@ describe('composite components', () => {
|
|
|
466
466
|
Edge<number>
|
|
467
467
|
>();
|
|
468
468
|
|
|
469
|
-
//
|
|
469
|
+
// 19. Ternary containing generics in both branches
|
|
470
470
|
let flag = true;
|
|
471
471
|
const s = flag ? new Box<number>() : new Box<string>();
|
|
472
472
|
|
|
@@ -477,55 +477,54 @@ describe('composite components', () => {
|
|
|
477
477
|
const registry = new TrackedMap<string, number>();
|
|
478
478
|
const u = registry.get<number>('id')?.toString();
|
|
479
479
|
|
|
480
|
-
//
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
// // 23. Generic followed by tagged template (ensure not confused with JSX)
|
|
487
|
-
// function tagFn<T>(strings: TemplateStringsArray, ...values: T[]) {
|
|
488
|
-
// return values[0];
|
|
489
|
-
// }
|
|
490
|
-
// const tagResult = tagFn<number>`value`;
|
|
480
|
+
// 22. Generic call used as callee for another call
|
|
481
|
+
function make<T>() {
|
|
482
|
+
return (value: T) => value;
|
|
483
|
+
}
|
|
484
|
+
const v = make<number>()(10);
|
|
491
485
|
|
|
492
|
-
//
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
486
|
+
// 23. Generic followed by tagged template (ensure not confused with JSX)
|
|
487
|
+
function tagFn<T>(strings: TemplateStringsArray, ...values: T[]) {
|
|
488
|
+
return values[0];
|
|
489
|
+
}
|
|
490
|
+
const tagResult = tagFn<number>`value`;
|
|
496
491
|
|
|
497
|
-
//
|
|
492
|
+
// 24. Sequence mixing: (a < b) + generic call in same statement
|
|
493
|
+
function compute<T>(x: T, y: T): T {
|
|
494
|
+
return y;
|
|
495
|
+
}
|
|
498
496
|
|
|
497
|
+
const w = (x < y) && compute<number>(x, y);
|
|
499
498
|
|
|
500
499
|
|
|
501
500
|
// Additional component focusing on edge crankers
|
|
502
501
|
|
|
503
|
-
//
|
|
502
|
+
// 28. Generic after parenthesized new expression
|
|
504
503
|
const aa = (new Box<number>()).open<string>();
|
|
505
504
|
|
|
506
|
-
//
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
//
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
//
|
|
505
|
+
// 29. Generic chain right after closing paren of IIFE
|
|
506
|
+
class Builder<Kind> {
|
|
507
|
+
finalize<Result>() {
|
|
508
|
+
return {
|
|
509
|
+
result: null as unknown as Result
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
const builder = new Builder<Number>();
|
|
514
|
+
const result = ((function(){ return builder; })() as Builder<Number>).finalize<boolean>();
|
|
515
|
+
|
|
516
|
+
// 30. Angle bracket start of conditional expression line
|
|
517
|
+
function adjust<T>(value: T): T {
|
|
518
|
+
return value;
|
|
519
|
+
}
|
|
520
|
+
const val =
|
|
521
|
+
new Wrapper<number>()
|
|
522
|
+
.value < 100
|
|
523
|
+
? adjust<number>(10)
|
|
524
|
+
: adjust<number>(20);
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
// 32. Generic with comments inside angle list
|
|
529
528
|
class Mapper<Key, Value> {
|
|
530
529
|
map: Map<Key, Value>;
|
|
531
530
|
constructor() {
|
|
@@ -539,7 +538,7 @@ describe('composite components', () => {
|
|
|
539
538
|
number
|
|
540
539
|
>();
|
|
541
540
|
|
|
542
|
-
//
|
|
541
|
+
// 33. Map of generic instance as key
|
|
543
542
|
const mm = new Map<TrackedArray<number>, TrackedArray<string>>();
|
|
544
543
|
}
|
|
545
544
|
|
|
@@ -667,9 +666,9 @@ describe('composite components', () => {
|
|
|
667
666
|
effect(() => {
|
|
668
667
|
logs.push(@count);
|
|
669
668
|
})
|
|
670
|
-
|
|
669
|
+
|
|
671
670
|
<button onClick={() => @count = @count + 1}>{'+'}</button>
|
|
672
|
-
}
|
|
671
|
+
}
|
|
673
672
|
|
|
674
673
|
component App() {
|
|
675
674
|
const count = track(0);
|
|
@@ -35,3 +35,11 @@ export async function App(__output) {
|
|
|
35
35
|
});
|
|
36
36
|
}"
|
|
37
37
|
`;
|
|
38
|
+
|
|
39
|
+
exports[`compiler success tests > removes type assertions from function parameters and leaves default values 1`] = `
|
|
40
|
+
"import * as _$_ from 'ripple/internal/client';
|
|
41
|
+
|
|
42
|
+
function getString(e = 'test') {
|
|
43
|
+
return e;
|
|
44
|
+
}"
|
|
45
|
+
`;
|
|
@@ -36,4 +36,15 @@ export component App() {
|
|
|
36
36
|
expect(result.js.code).toContain('await ChildComponent');
|
|
37
37
|
expect(result.js.code).toMatchSnapshot();
|
|
38
38
|
});
|
|
39
|
+
|
|
40
|
+
it('removes type assertions from function parameters and leaves default values', () => {
|
|
41
|
+
const source = `
|
|
42
|
+
function getString(e: string = 'test') {
|
|
43
|
+
return e;
|
|
44
|
+
}`;
|
|
45
|
+
|
|
46
|
+
const result = compile(source, 'test.ripple', { mode: 'client' });
|
|
47
|
+
|
|
48
|
+
expect(result.js.code).toMatchSnapshot();
|
|
49
|
+
});
|
|
39
50
|
});
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { render } from 'ripple/server';
|
|
3
|
+
import { TrackedArray, TrackedMap } from 'ripple';
|
|
4
|
+
|
|
5
|
+
describe('generics', () => {
|
|
6
|
+
it('handles advanced generic ambiguity and edge cases', () => {
|
|
7
|
+
component App() {
|
|
8
|
+
// Ambiguous generics vs JSX / less-than parsing scenarios
|
|
9
|
+
|
|
10
|
+
// 7. Generic following optional chaining
|
|
11
|
+
const maybe = {
|
|
12
|
+
factory<T>() {
|
|
13
|
+
return {
|
|
14
|
+
make<U>() {
|
|
15
|
+
return 1;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const g = maybe?.factory<number>()?.make<boolean>();
|
|
21
|
+
|
|
22
|
+
// 8. Comparison operator (ensure '<' here NOT misparsed as generics)
|
|
23
|
+
let x = 10, y = 20;
|
|
24
|
+
const h = x < y ? 'lt' : 'ge';
|
|
25
|
+
|
|
26
|
+
// 9. Chained comparisons with intervening generics
|
|
27
|
+
class Box<T> {
|
|
28
|
+
value: T;
|
|
29
|
+
constructor(value?: T) {
|
|
30
|
+
this.value = value;
|
|
31
|
+
}
|
|
32
|
+
open<U>() {
|
|
33
|
+
return new Box<U>();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const limit = 100;
|
|
37
|
+
const i = new Box<number>().value < limit ? 'ok' : 'no';
|
|
38
|
+
|
|
39
|
+
// 10. JSX / Element should still work
|
|
40
|
+
<div class="still-works">
|
|
41
|
+
<span>{'Test'}</span>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
// 11. Generic function call vs Element: Identifier followed by generic args
|
|
45
|
+
function identity<T>(value: T): T {
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
const j = identity<number>(42);
|
|
49
|
+
|
|
50
|
+
// 12. Member + generic call immediately followed by another call
|
|
51
|
+
class Factory {
|
|
52
|
+
create<T>() {
|
|
53
|
+
return (value: T) => value;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const factory = new Factory();
|
|
57
|
+
const k = factory.create<number>()(123);
|
|
58
|
+
|
|
59
|
+
// 13. Multiple generic segments in chain
|
|
60
|
+
function foo<T>() {
|
|
61
|
+
return {
|
|
62
|
+
bar<U>() {
|
|
63
|
+
return {
|
|
64
|
+
baz<V>() {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const l = foo<number>().bar<string>().baz<boolean>();
|
|
72
|
+
|
|
73
|
+
// 14. Generic with constraint + default
|
|
74
|
+
type Extractor<T extends { id: number } = { id: number }> = (v: T) => number;
|
|
75
|
+
const m: Extractor = (v) => v.id;
|
|
76
|
+
|
|
77
|
+
// 15. Generic in angle after "new" + trailing call
|
|
78
|
+
class Wrapper<T> {
|
|
79
|
+
value: T;
|
|
80
|
+
constructor() {
|
|
81
|
+
this.value = null as unknown as T;
|
|
82
|
+
}
|
|
83
|
+
unwrap<U>() {
|
|
84
|
+
return null as unknown as U;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const n = new Wrapper<number>().unwrap<string>();
|
|
88
|
+
|
|
89
|
+
// 16. Angle brackets inside type assertion vs generic call
|
|
90
|
+
function getUnknown(): unknown {
|
|
91
|
+
return new Map<string, number>([['a', 1]]);
|
|
92
|
+
}
|
|
93
|
+
getUnknown.factory = function<T>() {
|
|
94
|
+
return {
|
|
95
|
+
make<U>() {
|
|
96
|
+
return 2;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
const raw = getUnknown();
|
|
101
|
+
const o = (raw as Map<string, number>).get('a');
|
|
102
|
+
|
|
103
|
+
// 17. Generic with comma + trailing less-than comparison on next token
|
|
104
|
+
class Pair<T1, T2> {
|
|
105
|
+
first: T1;
|
|
106
|
+
second: T2;
|
|
107
|
+
constructor() {
|
|
108
|
+
this.first = null as unknown as T1;
|
|
109
|
+
this.second = null as unknown as T2;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const p = new Pair<number, string>();
|
|
113
|
+
const q = 1 < 2 ? p : null;
|
|
114
|
+
|
|
115
|
+
// 18. Nested generics with line breaks resembling JSX indentation
|
|
116
|
+
interface Node<T> {
|
|
117
|
+
value: T;
|
|
118
|
+
}
|
|
119
|
+
interface Edge<W> {
|
|
120
|
+
weight: W;
|
|
121
|
+
}
|
|
122
|
+
class Graph<N, E> {
|
|
123
|
+
nodes: N[];
|
|
124
|
+
edges: E[];
|
|
125
|
+
constructor() {
|
|
126
|
+
this.nodes = [];
|
|
127
|
+
this.edges = [];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const r = new Graph<
|
|
131
|
+
Node<string>,
|
|
132
|
+
Edge<number>
|
|
133
|
+
>();
|
|
134
|
+
|
|
135
|
+
// 19. Ternary containing generics in both branches
|
|
136
|
+
let flag = true;
|
|
137
|
+
const s = flag ? new Box<number>() : new Box<string>();
|
|
138
|
+
|
|
139
|
+
// 20. Generic inside template expression
|
|
140
|
+
const t = `length=${new TrackedArray<number>().length}`;
|
|
141
|
+
|
|
142
|
+
// 21. Optional chaining + generic + property access
|
|
143
|
+
const registry = new TrackedMap<string, number>();
|
|
144
|
+
const u = registry.get<number>('id')?.toString();
|
|
145
|
+
|
|
146
|
+
// 22. Generic call used as callee for another call
|
|
147
|
+
function make<T>() {
|
|
148
|
+
return (value: T) => value;
|
|
149
|
+
}
|
|
150
|
+
const v = make<number>()(10);
|
|
151
|
+
|
|
152
|
+
// 23. Generic followed by tagged template (ensure not confused with JSX)
|
|
153
|
+
function tagFn<T>(strings: TemplateStringsArray, ...values: T[]) {
|
|
154
|
+
return values[0];
|
|
155
|
+
}
|
|
156
|
+
const tagResult = tagFn<number>`value`;
|
|
157
|
+
|
|
158
|
+
// 24. Sequence mixing: (a < b) + generic call in same statement
|
|
159
|
+
function compute<T>(x: T, y: T): T {
|
|
160
|
+
return y;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const w = (x < y) && compute<number>(x, y);
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
// Additional component focusing on edge crankers
|
|
167
|
+
|
|
168
|
+
// 28. Generic after parenthesized new expression
|
|
169
|
+
const aa = (new Box<number>()).open<string>();
|
|
170
|
+
|
|
171
|
+
// 29. Generic chain right after closing paren of IIFE
|
|
172
|
+
class Builder<Kind> {
|
|
173
|
+
finalize<Result>() {
|
|
174
|
+
return {
|
|
175
|
+
result: null as unknown as Result
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const builder = new Builder<Number>();
|
|
180
|
+
const result = ((function(){ return builder; })() as Builder<Number>).finalize<boolean>();
|
|
181
|
+
|
|
182
|
+
// 30. Angle bracket start of conditional expression line
|
|
183
|
+
function adjust<T>(value: T): T {
|
|
184
|
+
return value;
|
|
185
|
+
}
|
|
186
|
+
const val =
|
|
187
|
+
new Wrapper<number>()
|
|
188
|
+
.value < 100
|
|
189
|
+
? adjust<number>(10)
|
|
190
|
+
: adjust<number>(20);
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
// 32. Generic with comments inside angle list
|
|
194
|
+
class Mapper<Key, Value> {
|
|
195
|
+
map: Map<Key, Value>;
|
|
196
|
+
constructor() {
|
|
197
|
+
this.map = new Map<Key, Value>();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const gg = new Mapper<
|
|
201
|
+
// key type
|
|
202
|
+
string,
|
|
203
|
+
/* value type */
|
|
204
|
+
number
|
|
205
|
+
>();
|
|
206
|
+
|
|
207
|
+
// 33. Map of generic instance as key
|
|
208
|
+
const mm = new Map<TrackedArray<number>, TrackedArray<string>>();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
render(App);
|
|
212
|
+
});
|
|
213
|
+
});
|