ripple 0.2.102 → 0.2.103

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 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.102",
6
+ "version": "0.2.103",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index-client.js",
9
9
  "main": "src/runtime/index-client.js",
@@ -64,8 +64,8 @@
64
64
  },
65
65
  "dependencies": {
66
66
  "@jridgewell/sourcemap-codec": "^1.5.5",
67
+ "@sveltejs/acorn-typescript": "^1.0.6",
67
68
  "acorn": "^8.15.0",
68
- "acorn-typescript": "^1.4.13",
69
69
  "clsx": "^2.1.1",
70
70
  "esrap": "^2.1.0",
71
71
  "is-reference": "^3.0.3",
@@ -5,12 +5,12 @@
5
5
  * } from '#compiler' */
6
6
 
7
7
  import * as acorn from 'acorn';
8
- import { tsPlugin } from 'acorn-typescript';
8
+ import { tsPlugin } from '@sveltejs/acorn-typescript';
9
9
  import { parse_style } from './style.js';
10
10
  import { walk } from 'zimmerframe';
11
11
  import { regex_newline_characters } from '../../../utils/patterns.js';
12
12
 
13
- const parser = acorn.Parser.extend(tsPlugin({ allowSatisfies: true }), RipplePlugin());
13
+ const parser = acorn.Parser.extend(tsPlugin({ jsx: true }), RipplePlugin());
14
14
 
15
15
  /**
16
16
  * Convert JSX node types to regular JavaScript node types
@@ -67,7 +67,51 @@ function RipplePlugin(config) {
67
67
  getTokenFromCode(code) {
68
68
  if (code === 60) {
69
69
  // < character
70
- if (this.#path.findLast((n) => n.type === 'Component')) {
70
+ const inComponent = this.#path.findLast((n) => n.type === 'Component');
71
+
72
+ // Check if this could be TypeScript generics instead of JSX
73
+ // TypeScript generics appear after: identifiers, closing parens, 'new' keyword
74
+ // For example: Array<T>, func<T>(), new Map<K,V>(), method<T>()
75
+ // This check applies everywhere, not just inside components
76
+
77
+ // Look back to see what precedes the <
78
+ let lookback = this.pos - 1;
79
+
80
+ // Skip whitespace backwards
81
+ while (lookback >= 0) {
82
+ const ch = this.input.charCodeAt(lookback);
83
+ if (ch !== 32 && ch !== 9) break; // not space or tab
84
+ lookback--;
85
+ }
86
+
87
+ // Check what character/token precedes the <
88
+ if (lookback >= 0) {
89
+ const prevChar = this.input.charCodeAt(lookback);
90
+
91
+ // If preceded by identifier character (letter, digit, _, $) or closing paren,
92
+ // this is likely TypeScript generics, not JSX
93
+ const isIdentifierChar =
94
+ (prevChar >= 65 && prevChar <= 90) || // A-Z
95
+ (prevChar >= 97 && prevChar <= 122) || // a-z
96
+ (prevChar >= 48 && prevChar <= 57) || // 0-9
97
+ prevChar === 95 || // _
98
+ prevChar === 36 || // $
99
+ prevChar === 41; // )
100
+
101
+ if (isIdentifierChar) {
102
+ return super.getTokenFromCode(code);
103
+ }
104
+ }
105
+
106
+ if (inComponent) {
107
+ // Inside nested functions (scopeStack.length >= 5), treat < as relational/generic operator
108
+ // At component top-level (scopeStack.length <= 4), apply JSX detection logic
109
+ if (this.scopeStack.length >= 5) {
110
+ // Inside function - treat as TypeScript generic, not JSX
111
+ ++this.pos;
112
+ return this.finishToken(tt.relational, '<');
113
+ }
114
+
71
115
  // Check if everything before this position on the current line is whitespace
72
116
  let lineStart = this.pos - 1;
73
117
  while (
@@ -214,6 +258,88 @@ function RipplePlugin(config) {
214
258
  return node;
215
259
  }
216
260
 
261
+ /**
262
+ * Override parseSubscripts to handle `.@[expression]` syntax for reactive computed member access
263
+ * @param {any} base - The base expression
264
+ * @param {number} startPos - Start position
265
+ * @param {any} startLoc - Start location
266
+ * @param {boolean} noCalls - Whether calls are disallowed
267
+ * @param {any} maybeAsyncArrow - Optional async arrow flag
268
+ * @param {any} optionalChained - Optional chaining flag
269
+ * @param {any} forInit - For-init flag
270
+ * @returns {any} Parsed subscript expression
271
+ */
272
+ parseSubscripts(
273
+ base,
274
+ startPos,
275
+ startLoc,
276
+ noCalls,
277
+ maybeAsyncArrow,
278
+ optionalChained,
279
+ forInit,
280
+ ) {
281
+ // Check for `.@[` pattern for reactive computed member access
282
+ const isDotOrOptional = this.type === tt.dot || this.type === tt.questionDot;
283
+
284
+ if (isDotOrOptional) {
285
+ // Check the next two characters without consuming tokens
286
+ // this.pos currently points AFTER the dot token
287
+ const nextChar = this.input.charCodeAt(this.pos);
288
+ const charAfter = this.input.charCodeAt(this.pos + 1);
289
+
290
+ // Check for @[ pattern (@ = 64, [ = 91)
291
+ if (nextChar === 64 && charAfter === 91) {
292
+ const node = this.startNodeAt(startPos, startLoc);
293
+ node.object = base;
294
+ node.computed = true;
295
+ node.optional = this.type === tt.questionDot;
296
+ node.tracked = true;
297
+
298
+ // Consume the dot/questionDot token
299
+ this.next();
300
+
301
+ // Manually skip the @ character
302
+ this.pos += 1;
303
+
304
+ // Now call finishToken to properly consume the [ bracket
305
+ this.finishToken(tt.bracketL);
306
+
307
+ // Now we're positioned correctly to parse the expression
308
+ this.next(); // Move to first token inside brackets
309
+
310
+ // Parse the expression inside brackets
311
+ node.property = this.parseExpression();
312
+
313
+ // Expect closing bracket
314
+ this.expect(tt.bracketR);
315
+
316
+ // Finish this MemberExpression node
317
+ base = this.finishNode(node, 'MemberExpression');
318
+
319
+ // Recursively handle any further subscripts (chaining)
320
+ return this.parseSubscripts(
321
+ base,
322
+ startPos,
323
+ startLoc,
324
+ noCalls,
325
+ maybeAsyncArrow,
326
+ optionalChained,
327
+ forInit,
328
+ );
329
+ }
330
+ }
331
+
332
+ // Fall back to default parseSubscripts implementation
333
+ return super.parseSubscripts(
334
+ base,
335
+ startPos,
336
+ startLoc,
337
+ noCalls,
338
+ maybeAsyncArrow,
339
+ optionalChained,
340
+ forInit,
341
+ );
342
+ }
217
343
  /**
218
344
  * Parse expression atom - handles TrackedArray and TrackedObject literals
219
345
  * @param {any} [refDestructuringErrors]
@@ -1230,7 +1356,7 @@ function get_comment_handlers(source, comments, index = 0) {
1230
1356
 
1231
1357
  comments = comments
1232
1358
  .filter((comment) => comment.start >= index)
1233
- .map(({ type, value, start, end }) => ({ type, value, start, end }));
1359
+ .map(({ type, value, start, end, loc }) => ({ type, value, start, end, loc }));
1234
1360
 
1235
1361
  walk(ast, null, {
1236
1362
  _(node, { next, path }) {
@@ -1301,11 +1427,56 @@ function get_comment_handlers(source, comments, index = 0) {
1301
1427
  export function parse(source) {
1302
1428
  /** @type {CommentWithLocation[]} */
1303
1429
  const comments = [];
1304
- const { onComment, add_comments } = get_comment_handlers(source, comments);
1430
+
1431
+ // Preprocess step 1: Add trailing commas to single-parameter generics followed by (
1432
+ // This is a workaround for @sveltejs/acorn-typescript limitations with JSX enabled
1433
+ let preprocessedSource = source;
1434
+ let sourceChanged = false;
1435
+
1436
+ preprocessedSource = source.replace(/(<\s*[A-Z][a-zA-Z0-9_$]*\s*)>\s*\(/g, (_, generic) => {
1437
+ sourceChanged = true;
1438
+ // Add trailing comma to disambiguate from JSX
1439
+ return `${generic},>(`;
1440
+ });
1441
+
1442
+ // Preprocess step 2: Convert generic method shorthand in object literals to function property syntax
1443
+ // Transform `method<T,>(...): ReturnType { body }` to `method: function<T,>(...): ReturnType { body }`
1444
+ // Note: This only applies to object literal methods, not class methods
1445
+ // The trailing comma was already added by step 1
1446
+ preprocessedSource = preprocessedSource.replace(
1447
+ /(\w+)(<[A-Z][a-zA-Z0-9_$,\s]*>)\s*\(([^)]*)\)(\s*:\s*[^{]+)?(\s*\{)/g,
1448
+ (match, methodName, generics, params, returnType, brace, offset) => {
1449
+ // Look backward to determine context
1450
+ let checkPos = offset - 1;
1451
+ while (checkPos >= 0 && /\s/.test(preprocessedSource[checkPos])) checkPos--;
1452
+ const prevChar = preprocessedSource[checkPos];
1453
+
1454
+ // Check if we're inside a class
1455
+ const before = preprocessedSource.substring(Math.max(0, offset - 500), offset);
1456
+ const classMatch = before.match(/\bclass\s+\w+[^{]*\{[^}]*$/);
1457
+
1458
+ // Only transform if we're in an object literal context AND not inside a class
1459
+ if ((prevChar === '{' || prevChar === ',') && !classMatch) {
1460
+ sourceChanged = true;
1461
+ // This is object literal method shorthand - convert to function property
1462
+ // Add trailing comma if not already present
1463
+ const fixedGenerics = generics.includes(',') ? generics : generics.replace('>', ',>');
1464
+ return `${methodName}: function${fixedGenerics}(${params})${returnType || ''}${brace}`;
1465
+ }
1466
+ return match;
1467
+ },
1468
+ );
1469
+
1470
+ // Only mark as preprocessed if we actually changed something
1471
+ if (!sourceChanged) {
1472
+ preprocessedSource = source;
1473
+ }
1474
+
1475
+ const { onComment, add_comments } = get_comment_handlers(preprocessedSource, comments);
1305
1476
  let ast;
1306
1477
 
1307
1478
  try {
1308
- ast = parser.parse(source, {
1479
+ ast = parser.parse(preprocessedSource, {
1309
1480
  sourceType: 'module',
1310
1481
  ecmaVersion: 13,
1311
1482
  locations: true,
@@ -1,4 +1,4 @@
1
- /** @import {Expression, FunctionExpression} from 'estree' */
1
+ /** @import {Expression, FunctionExpression, Pattern} from 'estree' */
2
2
 
3
3
  import { walk } from 'zimmerframe';
4
4
  import path from 'node:path';
@@ -293,7 +293,6 @@ const visitors = {
293
293
 
294
294
  NewExpression(node, context) {
295
295
  const callee = node.callee;
296
- const parent = context.path.at(-1);
297
296
 
298
297
  if (context.state.metadata?.tracking === false) {
299
298
  context.state.metadata.tracking = true;
@@ -305,18 +304,22 @@ const visitors = {
305
304
  is_inside_call_expression(context) ||
306
305
  is_value_static(node)
307
306
  ) {
307
+ if (!context.state.to_ts) {
308
+ delete node.typeArguments;
309
+ }
308
310
  return context.next();
309
311
  }
310
312
 
311
- return b.call(
312
- '_$_.with_scope',
313
- b.id('__block'),
314
- b.thunk({
315
- ...node,
316
- callee: context.visit(callee),
317
- arguments: node.arguments.map((arg) => context.visit(arg)),
318
- }),
319
- );
313
+ const new_node = {
314
+ ...node,
315
+ callee: context.visit(callee),
316
+ arguments: node.arguments.map((arg) => context.visit(arg)),
317
+ };
318
+ if (!context.state.to_ts) {
319
+ delete new_node.typeArguments;
320
+ }
321
+
322
+ return b.call('_$_.with_scope', b.id('__block'), b.thunk(new_node));
320
323
  },
321
324
 
322
325
  TrackedArrayExpression(node, context) {
@@ -366,7 +369,7 @@ const visitors = {
366
369
  context.state.metadata.tracking = true;
367
370
  }
368
371
 
369
- if (node.property.type === 'Identifier' && node.property.tracked) {
372
+ if (node.tracked || (node.property.type === 'Identifier' && node.property.tracked)) {
370
373
  add_ripple_internal_import(context);
371
374
 
372
375
  return b.call(
@@ -918,8 +921,7 @@ const visitors = {
918
921
 
919
922
  if (
920
923
  left.type === 'MemberExpression' &&
921
- left.property.type === 'Identifier' &&
922
- left.property.tracked
924
+ (left.tracked || (left.property.type === 'Identifier' && left.property.tracked))
923
925
  ) {
924
926
  add_ripple_internal_import(context);
925
927
  const operator = node.operator;
@@ -976,8 +978,7 @@ const visitors = {
976
978
 
977
979
  if (
978
980
  argument.type === 'MemberExpression' &&
979
- argument.property.type === 'Identifier' &&
980
- argument.property.tracked
981
+ (argument.tracked || (argument.property.type === 'Identifier' && argument.property.tracked))
981
982
  ) {
982
983
  add_ripple_internal_import(context);
983
984
  context.state.metadata.tracking = true;
@@ -1052,7 +1053,9 @@ const visitors = {
1052
1053
  ),
1053
1054
  ),
1054
1055
  b.literal(flags),
1055
- key != null ? b.arrow(index ? [pattern, index] : [pattern], context.visit(key)) : undefined,
1056
+ key != null
1057
+ ? b.arrow(index ? [pattern, index] : [pattern], context.visit(key))
1058
+ : undefined,
1056
1059
  ),
1057
1060
  ),
1058
1061
  );
@@ -1401,6 +1404,9 @@ function transform_ts_child(node, context) {
1401
1404
  ...context,
1402
1405
  state: { ...context.state, scope: body_scope },
1403
1406
  });
1407
+ if (node.key) {
1408
+ block_body.unshift(b.stmt(visit(node.key)));
1409
+ }
1404
1410
  if (node.index) {
1405
1411
  block_body.unshift(b.let(visit(node.index), b.literal(0)));
1406
1412
  }
@@ -0,0 +1,49 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`computed tracked properties > should update a property using assignment 1`] = `
4
+ <div>
5
+ <div>
6
+ 0
7
+ </div>
8
+ <button>
9
+ Increment
10
+ </button>
11
+
12
+ </div>
13
+ `;
14
+
15
+ exports[`computed tracked properties > should update a property using assignment 2`] = `
16
+ <div>
17
+ <div>
18
+ 1
19
+ </div>
20
+ <button>
21
+ Increment
22
+ </button>
23
+
24
+ </div>
25
+ `;
26
+
27
+ exports[`computed tracked properties > should update a property using update expressions 1`] = `
28
+ <div>
29
+ <div>
30
+ 0
31
+ </div>
32
+ <button>
33
+ Increment
34
+ </button>
35
+
36
+ </div>
37
+ `;
38
+
39
+ exports[`computed tracked properties > should update a property using update expressions 2`] = `
40
+ <div>
41
+ <div>
42
+ 1
43
+ </div>
44
+ <button>
45
+ Increment
46
+ </button>
47
+
48
+ </div>
49
+ `;
@@ -342,25 +342,33 @@ describe('composite components', () => {
342
342
  // Ambiguous generics vs JSX / less-than parsing scenarios
343
343
 
344
344
  // // 7. Generic following optional chaining
345
- // const maybe = {};
346
- // const g = maybe?.factory<number>()?.make<boolean>();
345
+ const maybe = {
346
+ factory<T>() {
347
+ return {
348
+ make<U>() {
349
+ return 1;
350
+ }
351
+ };
352
+ }
353
+ };
354
+ const g = maybe?.factory<number>()?.make<boolean>();
347
355
 
348
356
  // // 8. Comparison operator (ensure '<' here NOT misparsed as generics)
349
- // let x = 10, y = 20;
350
- // const h = x < y ? 'lt' : 'ge';
357
+ let x = 10, y = 20;
358
+ const h = x < y ? 'lt' : 'ge';
351
359
 
352
360
  // // 9. Chained comparisons with intervening generics
353
- // class Box<T> {
354
- // value: T;
355
- // constructor(value?: T) {
356
- // this.value = value;
357
- // }
358
- // open<U>() {
359
- // return new Box<U>();
360
- // }
361
- // }
362
- // const limit = 100;
363
- // const i = new Box<number>().value < limit ? 'ok' : 'no';
361
+ class Box<T> {
362
+ value: T;
363
+ constructor(value?: T) {
364
+ this.value = value;
365
+ }
366
+ open<U>() {
367
+ return new Box<U>();
368
+ }
369
+ }
370
+ const limit = 100;
371
+ const i = new Box<number>().value < limit ? 'ok' : 'no';
364
372
 
365
373
  // 10. JSX / Element should still work
366
374
  <div class="still-works">
@@ -401,16 +409,16 @@ describe('composite components', () => {
401
409
  const m: Extractor = (v) => v.id;
402
410
 
403
411
  // // 15. Generic in angle after "new" + trailing call
404
- // class Wrapper<T> {
405
- // value: T;
406
- // constructor() {
407
- // this.value = null as unknown as T;
408
- // }
409
- // unwrap<U>() {
410
- // return null as unknown as U;
411
- // }
412
- // }
413
- // const n = new Wrapper<number>().unwrap<string>();
412
+ class Wrapper<T> {
413
+ value: T;
414
+ constructor() {
415
+ this.value = null as unknown as T;
416
+ }
417
+ unwrap<U>() {
418
+ return null as unknown as U;
419
+ }
420
+ }
421
+ const n = new Wrapper<number>().unwrap<string>();
414
422
 
415
423
  // // 16. Angle brackets inside type assertion vs generic call
416
424
  // function getUnknown(): unknown {
@@ -427,40 +435,40 @@ describe('composite components', () => {
427
435
  // const o = (raw as Map<string, number>).get('a');
428
436
 
429
437
  // // 17. Generic with comma + trailing less-than comparison on next token
430
- // class Pair<T1, T2> {
431
- // first: T1;
432
- // second: T2;
433
- // constructor() {
434
- // this.first = null as unknown as T1;
435
- // this.second = null as unknown as T2;
436
- // }
437
- // }
438
- // const p = new Pair<number, string>();
439
- // const q = 1 < 2 ? p : null;
438
+ class Pair<T1, T2> {
439
+ first: T1;
440
+ second: T2;
441
+ constructor() {
442
+ this.first = null as unknown as T1;
443
+ this.second = null as unknown as T2;
444
+ }
445
+ }
446
+ const p = new Pair<number, string>();
447
+ const q = 1 < 2 ? p : null;
440
448
 
441
449
  // // 18. Nested generics with line breaks resembling JSX indentation
442
- // interface Node<T> {
443
- // value: T;
444
- // }
445
- // interface Edge<W> {
446
- // weight: W;
447
- // }
448
- // class Graph<N, E> {
449
- // nodes: N[];
450
- // edges: E[];
451
- // constructor() {
452
- // this.nodes = [];
453
- // this.edges = [];
454
- // }
455
- // }
456
- // const r = new Graph<
457
- // Node<string>,
458
- // Edge<number>
459
- // >();
450
+ interface Node<T> {
451
+ value: T;
452
+ }
453
+ interface Edge<W> {
454
+ weight: W;
455
+ }
456
+ class Graph<N, E> {
457
+ nodes: N[];
458
+ edges: E[];
459
+ constructor() {
460
+ this.nodes = [];
461
+ this.edges = [];
462
+ }
463
+ }
464
+ const r = new Graph<
465
+ Node<string>,
466
+ Edge<number>
467
+ >();
460
468
 
461
469
  // // 19. Ternary containing generics in both branches
462
- // let flag = true;
463
- // const s = flag ? new Box<number>() : new Box<string>();
470
+ let flag = true;
471
+ const s = flag ? new Box<number>() : new Box<string>();
464
472
 
465
473
  // 20. Generic inside template expression
466
474
  const t = `length=${new TrackedArray<number>().length}`;
@@ -473,7 +481,7 @@ describe('composite components', () => {
473
481
  // function make<T>() {
474
482
  // return (value: T) => value;
475
483
  // }
476
- // const v = make<number>()('done');
484
+ // const v = make<number>()(10);
477
485
 
478
486
  // // 23. Generic followed by tagged template (ensure not confused with JSX)
479
487
  // function tagFn<T>(strings: TemplateStringsArray, ...values: T[]) {
@@ -493,7 +501,7 @@ describe('composite components', () => {
493
501
  // Additional component focusing on edge crankers
494
502
 
495
503
  // // 28. Generic after parenthesized new expression
496
- // const aa = (new Box<number>()).open<string>();
504
+ const aa = (new Box<number>()).open<string>();
497
505
 
498
506
  // // 29. Generic chain right after closing paren of IIFE
499
507
  // class Builder<Kind> {
@@ -518,28 +526,23 @@ describe('composite components', () => {
518
526
 
519
527
 
520
528
  // // 32. Generic with comments inside angle list
521
- // class Mapper<Key, Value> {
522
- // map: Map<Key, Value>;
523
- // constructor() {
524
- // this.map = new Map<Key, Value>();
525
- // }
526
- // }
527
- // const gg = new Mapper<
528
- // // key type
529
- // string,
530
- // /* value type */
531
- // number
532
- // >();
529
+ class Mapper<Key, Value> {
530
+ map: Map<Key, Value>;
531
+ constructor() {
532
+ this.map = new Map<Key, Value>();
533
+ }
534
+ }
535
+ const gg = new Mapper<
536
+ // key type
537
+ string,
538
+ /* value type */
539
+ number
540
+ >();
533
541
 
534
542
  // // 33. Map of generic instance as key
535
- // const mm = new Map<TrackedArray<number>, TrackedArray<string>>();
536
-
537
- // // 34. Inline assertion then generic call
538
-
539
- // const asserted = (getUnknown() as Factory).create<number>();
543
+ const mm = new Map<TrackedArray<number>, TrackedArray<string>>();
540
544
  }
541
545
 
542
-
543
546
  render(App);
544
547
  });
545
548
 
@@ -0,0 +1,64 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { mount, flushSync, track, TrackedArray, TrackedMap, effect } from 'ripple';
3
+
4
+ describe('computed tracked properties', () => {
5
+ let container;
6
+
7
+ function render(component) {
8
+ mount(component, {
9
+ target: container
10
+ });
11
+ }
12
+
13
+ beforeEach(() => {
14
+ container = document.createElement('div');
15
+ document.body.appendChild(container);
16
+ });
17
+
18
+ afterEach(() => {
19
+ document.body.removeChild(container);
20
+ container = null;
21
+ });
22
+
23
+ it('should update a property using assignment', () => {
24
+ component App() {
25
+ let obj = {
26
+ [0]: track(0),
27
+ };
28
+
29
+ <div>{obj.@[0]}</div>
30
+
31
+ <button onClick={() => { obj.@[0] += 1 }}>{"Increment"}</button>
32
+ }
33
+
34
+ render(App);
35
+ expect(container).toMatchSnapshot();
36
+
37
+ const button = container.querySelector('button');
38
+ button.click();
39
+ flushSync();
40
+
41
+ expect(container).toMatchSnapshot();
42
+ });
43
+
44
+ it('should update a property using update expressions', () => {
45
+ component App() {
46
+ let obj = {
47
+ [0]: track(0),
48
+ };
49
+
50
+ <div>{obj.@[0]}</div>
51
+
52
+ <button onClick={() => { obj.@[0]++ }}>{"Increment"}</button>
53
+ }
54
+
55
+ render(App);
56
+ expect(container).toMatchSnapshot();
57
+
58
+ const button = container.querySelector('button');
59
+ button.click();
60
+ flushSync();
61
+
62
+ expect(container).toMatchSnapshot();
63
+ });
64
+ });
@@ -65,38 +65,149 @@ describe('generic patterns', () => {
65
65
  render(App);
66
66
  });
67
67
 
68
- /**
69
- * Complex generics tests
70
- * These currently break the parser
71
- * We can't just use skip and then the tests are still parsed and compiled
72
- */
73
- /*
74
- describe('complex generics', () => {
75
- it('tests after a call expression result', () => {
76
- component App() {
77
- function getBuilder() {
78
- return <T>() => ({
79
- build<U>(): { build<V>(): V; data: T; key: U } {
80
- return {
81
- build<V>(): V {
82
- return 42 as V;
83
- },
84
- data: undefined as T,
85
- key: undefined as U
86
- };
68
+ it ('tests simple generic function with return type', () => {
69
+ component App() {
70
+ function getBuilder() {
71
+ return {
72
+ build: function<T>() { return 'test' }
73
+ }
74
+ }
75
+ }
76
+
77
+ render(App);
78
+ })
79
+
80
+ it ('tests simple generic function with return type and no arrow function', () => {
81
+ component App() {
82
+ function getBuilder() {
83
+ return {
84
+ build<T>() { return 'test' }
85
+ }
86
+ }
87
+
88
+ <div class="still-works">
89
+ <span>{'Test'}</span>
90
+ </div>
91
+
92
+ const f = getBuilder().build<string>();
93
+ }
94
+
95
+ render(App);
96
+ })
97
+
98
+ it ('tests simple generic arrow function with return type', () => {
99
+ component App() {
100
+ function getBuilder() {
101
+ return <T>() => ({
102
+ build<U>() { return 'test' }
103
+ })
104
+ }
105
+
106
+ <div class="still-works">
107
+ <span>{'Test'}</span>
108
+ </div>
109
+
110
+ type ResultType = string;
111
+ const f = getBuilder()<ResultType>().build<string>();
112
+ <div>{f}</div>
113
+ }
114
+
115
+ render(App);
116
+ })
117
+
118
+ it ('tests simple generic arrow function with explicit return type', () => {
119
+ component App() {
120
+ function getBuilder() {
121
+ return <T>() => ({
122
+ build<U>(): U { return 'test' }
123
+ })
124
+ }
125
+
126
+ <div class="still-works">
127
+ <span>{'Test'}</span>
128
+ </div>
129
+
130
+ type ResultType = string;
131
+ const f = getBuilder()<ResultType>().build<string>();
132
+ <div>{f}</div>
133
+ }
134
+
135
+ render(App);
136
+ })
137
+
138
+ it ('tests complex generic arrow function with return type', () => {
139
+ component App() {
140
+ function getBuilder() {
141
+ return <T>() => ({
142
+ build<U>(): { build: <V>() => V; data: T; key: U } {
143
+ return 'test';
144
+ }
145
+ })
146
+ }
147
+
148
+ <div class="still-works">
149
+ <span>{'Test'}</span>
150
+ </div>
151
+
152
+ type ResultType = string;
153
+ const f = getBuilder()<ResultType>().build<string>();
154
+ <div>{f}</div>
155
+ }
156
+
157
+ render(App);
158
+ })
159
+
160
+ it ('tests more complex generic arrow function with return type', () => {
161
+ component App() {
162
+ function getBuilder() {
163
+ return <T>() => ({
164
+ build<U>(): { build: <V>() => V; data: T; key: U } {
165
+ return {
166
+ build<V>(): V {
167
+ return 'test' as V;
87
168
  }
88
- });
169
+ };
89
170
  }
171
+ });
172
+ }
90
173
 
91
- <div class="still-works">
92
- <span>{'Test'}</span>
93
- </div>
174
+ <div class="still-works">
175
+ <span>{'Test'}</span>
176
+ </div>
94
177
 
95
- type ResultType = string;
178
+ type ResultType = string;
179
+ const f = getBuilder()<ResultType>().build<string>();
180
+ <div>{f}</div>
181
+ }
96
182
 
97
- const f = getBuilder()<ResultType>().build<number>();
98
- }
99
- });
100
- });
101
- */
183
+ render(App);
184
+ })
185
+
186
+ it ('tests most complex generic arrow function with return type', () => {
187
+ component App() {
188
+ function getBuilder() {
189
+ return <T>() => ({
190
+ build<U>(): { build: <V>() => V; data: T; key: U } {
191
+ return {
192
+ build<V>(): V {
193
+ return 42 as V;
194
+ },
195
+ data: undefined as T,
196
+ key: undefined as U
197
+ };
198
+ }
199
+ });
200
+ }
201
+
202
+ <div class="still-works">
203
+ <span>{'Test'}</span>
204
+ </div>
205
+
206
+ type ResultType = string;
207
+ const f = getBuilder()<ResultType>().build<string>();
208
+ <div>{f}</div>
209
+ }
210
+
211
+ render(App);
212
+ })
102
213
  });