@nudojs/cli 0.2.0 → 0.2.1

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 (60) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +34 -0
  3. package/dist/chunk-3HCPXPEW.js +5286 -0
  4. package/dist/evaluator-api.d.ts +94 -2
  5. package/dist/evaluator-api.js +187 -3
  6. package/dist/index.js +595 -130
  7. package/examples/real-world.js +66 -0
  8. package/examples/validate-real-world.ts +160 -0
  9. package/package.json +17 -2
  10. package/src/__tests__/builtin-advanced.test.ts +292 -0
  11. package/src/__tests__/builtin-api.test.ts +308 -0
  12. package/src/__tests__/builtin-functions.test.ts +313 -0
  13. package/src/__tests__/builtin-map.test.ts +129 -0
  14. package/src/__tests__/builtin-promise.test.ts +150 -0
  15. package/src/__tests__/builtin-regexp.test.ts +133 -0
  16. package/src/__tests__/builtin-set.test.ts +163 -0
  17. package/src/__tests__/builtin-url.test.ts +256 -0
  18. package/src/__tests__/builtin-web.test.ts +340 -0
  19. package/src/__tests__/combination-scenarios.test.ts +384 -0
  20. package/src/__tests__/edge-cases.test.ts +749 -0
  21. package/src/__tests__/evaluator-loops.test.ts +120 -0
  22. package/src/__tests__/evaluator-nullish-coalescing.test.ts +86 -0
  23. package/src/__tests__/evaluator-optional-chaining.test.ts +37 -0
  24. package/src/__tests__/evaluator-phase3.test.ts +353 -2
  25. package/src/__tests__/evaluator.test.ts +702 -2
  26. package/src/__tests__/mock-arrow-function.test.ts +169 -0
  27. package/src/__tests__/mock-comparison.test.ts +221 -0
  28. package/src/__tests__/mock-comprehensive.test.ts +250 -0
  29. package/src/__tests__/mock-final-integration.test.ts +263 -0
  30. package/src/__tests__/mock-helpers.test.ts +174 -0
  31. package/src/__tests__/mock-realworld.test.ts +191 -0
  32. package/src/__tests__/mock-rest-debug.test.ts +110 -0
  33. package/src/__tests__/mock-rest-edge.test.ts +176 -0
  34. package/src/__tests__/mock-sinon-runtime.test.ts +168 -0
  35. package/src/__tests__/narrowing-discriminated.test.ts +35 -0
  36. package/src/__tests__/narrowing-in-operator.test.ts +56 -0
  37. package/src/__tests__/narrowing-switch.test.ts +56 -0
  38. package/src/__tests__/narrowing-truthiness.test.ts +52 -0
  39. package/src/__tests__/nudo-mock-syntax.test.ts +245 -0
  40. package/src/__tests__/realworld-patterns.test.ts +430 -0
  41. package/src/__tests__/syntax-sugar.test.ts +345 -0
  42. package/src/builtins/builtin-intl.ts +17 -0
  43. package/src/builtins/builtin-map.ts +174 -0
  44. package/src/builtins/builtin-promise.ts +116 -0
  45. package/src/builtins/builtin-reflect.ts +30 -0
  46. package/src/builtins/builtin-regexp.ts +34 -0
  47. package/src/builtins/builtin-set.ts +131 -0
  48. package/src/builtins/builtin-symbol.ts +16 -0
  49. package/src/builtins/builtin-url.ts +58 -0
  50. package/src/builtins/builtin-weak.ts +22 -0
  51. package/src/builtins/builtin-web.ts +111 -0
  52. package/src/builtins/index.ts +61 -0
  53. package/src/config.ts +32 -0
  54. package/src/env-loader.ts +174 -0
  55. package/src/evaluator-api.ts +20 -0
  56. package/src/evaluator.ts +4018 -342
  57. package/src/index.ts +776 -137
  58. package/src/narrowing.ts +230 -15
  59. package/src/resolve-npm.ts +63 -0
  60. package/dist/chunk-LGWVGEMB.js +0 -1817
@@ -0,0 +1,749 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { parse, extractDirectives, type CaseDirective } from "@nudojs/parser";
3
+ import { typeValueToString, createEnvironment } from "@nudojs/core";
4
+ import { evaluateFunctionFull } from "../evaluator.js";
5
+
6
+ function runTest(source: string): { name: string; caseName: string; result: string }[] {
7
+ const ast = parse(source);
8
+ const directives = extractDirectives(ast);
9
+ const env = createEnvironment();
10
+ const results: { name: string; caseName: string; result: string }[] = [];
11
+
12
+ for (const fn of directives) {
13
+ const caseDirectives = fn.directives.filter((d): d is CaseDirective => d.kind === "case");
14
+ for (const dir of caseDirectives) {
15
+ const result = evaluateFunctionFull(fn.node, dir.args, env);
16
+ results.push({
17
+ name: fn.name,
18
+ caseName: dir.name,
19
+ result: typeValueToString(result.value),
20
+ });
21
+ }
22
+ }
23
+ return results;
24
+ }
25
+
26
+ describe("Edge Case Tests", () => {
27
+ describe("Empty Values", () => {
28
+ it("empty array", () => {
29
+ const results = runTest(`
30
+ // @nudo:case "empty-arr" ([])
31
+ function process(arr) {
32
+ return arr.length;
33
+ }
34
+ `);
35
+ expect(results[0].result).toBe("0");
36
+ });
37
+
38
+ it("empty object", () => {
39
+ const results = runTest(`
40
+ // @nudo:case "empty-obj" ({})
41
+ function process(obj) {
42
+ return Object.keys(obj).length;
43
+ }
44
+ `);
45
+ expect(results[0].result).toBe("0");
46
+ });
47
+
48
+ it("empty string", () => {
49
+ const results = runTest(`
50
+ // @nudo:case "empty-str" ("")
51
+ function process(str) {
52
+ return str.length;
53
+ }
54
+ `);
55
+ expect(results[0].result).toBe("0");
56
+ });
57
+
58
+ it("null handling", () => {
59
+ const results = runTest(`
60
+ // @nudo:case "null" (null)
61
+ function process(val) {
62
+ if (val === null) return "null";
63
+ return "not null";
64
+ }
65
+ `);
66
+ expect(results[0].result).toBe('"null"');
67
+ });
68
+
69
+ it("undefined handling", () => {
70
+ const results = runTest(`
71
+ // @nudo:case "undefined" (undefined)
72
+ function process(val) {
73
+ if (val === undefined) return "undefined";
74
+ return "defined";
75
+ }
76
+ `);
77
+ expect(results[0].result).toBe('"undefined"');
78
+ });
79
+
80
+ it("null to undefined", () => {
81
+ const results = runTest(`
82
+ // @nudo:case "null-undef" (null)
83
+ function process(val) {
84
+ return val ?? "default";
85
+ }
86
+ `);
87
+ expect(results[0].result).toBe('"default"');
88
+ });
89
+ });
90
+
91
+ describe("Type Conversions", () => {
92
+ it("number to string", () => {
93
+ const results = runTest(`
94
+ // @nudo:case "num-to-str" (42)
95
+ function convert(n) {
96
+ return String(n);
97
+ }
98
+ `);
99
+ expect(results[0].result).toBe('"42"');
100
+ });
101
+
102
+ it("string to number", () => {
103
+ const results = runTest(`
104
+ // @nudo:case "str-to-num" ("42")
105
+ function convert(s) {
106
+ return Number(s);
107
+ }
108
+ `);
109
+ expect(results[0].result).toBe("42");
110
+ });
111
+
112
+ it("boolean to number", () => {
113
+ const results = runTest(`
114
+ // @nudo:case "bool-to-num" (true)
115
+ function convert(b) {
116
+ return Number(b);
117
+ }
118
+ `);
119
+ expect(results[0].result).toBe("1");
120
+ });
121
+
122
+ it("number to boolean", () => {
123
+ const results = runTest(`
124
+ // @nudo:case "num-to-bool" (0)
125
+ function convert(n) {
126
+ return Boolean(n);
127
+ }
128
+ `);
129
+ expect(results[0].result).toBe("false");
130
+ });
131
+
132
+ it("string concatenation with number", () => {
133
+ const results = runTest(`
134
+ // @nudo:case "concat-num" ("value: ", 42)
135
+ function concat(s, n) {
136
+ return s + n;
137
+ }
138
+ `);
139
+ expect(results[0].result).toBe('"value: 42"');
140
+ });
141
+
142
+ it("parseInt", () => {
143
+ const results = runTest(`
144
+ // @nudo:case "parse-int" ("42")
145
+ function convert(s) {
146
+ return parseInt(s);
147
+ }
148
+ `);
149
+ expect(results[0].result).toBe("number");
150
+ });
151
+
152
+ it("parseFloat", () => {
153
+ const results = runTest(`
154
+ // @nudo:case "parse-float" ("3.14")
155
+ function convert(s) {
156
+ return parseFloat(s);
157
+ }
158
+ `);
159
+ expect(results[0].result).toBe("number");
160
+ });
161
+ });
162
+
163
+ describe("Boundary Values", () => {
164
+ it("max safe integer", () => {
165
+ const results = runTest(`
166
+ // @nudo:case "max-int" ()
167
+ function getMax() {
168
+ return Number.MAX_SAFE_INTEGER;
169
+ }
170
+ `);
171
+ // Design limitation: Number.MAX_SAFE_INTEGER not resolved → 已修复:
172
+ // BUILTIN_STATIC_METHODS.Number 补常量接线,现在解析为 number
173
+ expect(results[0].result).toBe("number");
174
+ });
175
+
176
+ it("min safe integer", () => {
177
+ const results = runTest(`
178
+ // @nudo:case "min-int" ()
179
+ function getMin() {
180
+ return Number.MIN_SAFE_INTEGER;
181
+ }
182
+ `);
183
+ // Number.MIN_SAFE_INTEGER 常量接线修复后解析为 number
184
+ expect(results[0].result).toBe("number");
185
+ });
186
+
187
+ it("infinity", () => {
188
+ const results = runTest(`
189
+ // @nudo:case "infinity" ()
190
+ function getInf() {
191
+ return Infinity;
192
+ }
193
+ `);
194
+ // Unresolved built-in globals propagate unknown (not undefined) → 已修复:
195
+ // Infinity 进入 BUILTIN_STATIC_METHODS,解析为 number(不再触发
196
+ // nudo:unknown-global / nudo:builtin-unknown)
197
+ expect(results[0].result).toBe("number");
198
+ });
199
+
200
+ it("negative infinity", () => {
201
+ const results = runTest(`
202
+ // @nudo:case "neg-infinity" ()
203
+ function getNegInf() {
204
+ return -Infinity;
205
+ }
206
+ `);
207
+ // Unary - on undefined returns number → 已修复:- 现在作用于 number
208
+ expect(results[0].result).toBe("number");
209
+ });
210
+
211
+ it("NaN", () => {
212
+ const results = runTest(`
213
+ // @nudo:case "nan" ()
214
+ function getNaN() {
215
+ return NaN;
216
+ }
217
+ `);
218
+ // Unresolved built-in globals propagate unknown (not undefined) → 已修复:
219
+ // NaN 进入 BUILTIN_STATIC_METHODS,解析为 number
220
+ expect(results[0].result).toBe("number");
221
+ });
222
+
223
+ it("Math.PI / Math.E", () => {
224
+ const results = runTest(`
225
+ // @nudo:case "math-pi" ()
226
+ // @nudo:case "math-e" ()
227
+ function constants() {
228
+ return [Math.PI, Math.E];
229
+ }
230
+ `);
231
+ // Math 常量接线修复:解析为 number(数组字面量求值为 tuple)
232
+ expect(results[0].result).toBe("[number, number]");
233
+ });
234
+
235
+ it("Number.MAX_VALUE / Number.EPSILON", () => {
236
+ const results = runTest(`
237
+ // @nudo:case "num-consts" ()
238
+ function constants() {
239
+ return [Number.MAX_VALUE, Number.EPSILON];
240
+ }
241
+ `);
242
+ // Number 常量接线修复:解析为 number(数组字面量求值为 tuple)
243
+ expect(results[0].result).toBe("[number, number]");
244
+ });
245
+
246
+ it("empty array index", () => {
247
+ const results = runTest(`
248
+ // @nudo:case "empty-idx" ([])
249
+ function getFirst(arr) {
250
+ return arr[0];
251
+ }
252
+ `);
253
+ expect(results[0].result).toBe("undefined");
254
+ });
255
+
256
+ it("out of bounds index", () => {
257
+ const results = runTest(`
258
+ // @nudo:case "oob-idx" ([1, 2, 3])
259
+ function getOob(arr) {
260
+ return arr[10];
261
+ }
262
+ `);
263
+ expect(results[0].result).toBe("undefined");
264
+ });
265
+ });
266
+
267
+ describe("Error Handling Edge Cases", () => {
268
+ it("throw string", () => {
269
+ const results = runTest(`
270
+ // @nudo:case "throw-str" ()
271
+ function throwError() {
272
+ throw "error";
273
+ }
274
+ `);
275
+ expect(results[0].result).toBe("never");
276
+ });
277
+
278
+ it("throw object", () => {
279
+ const results = runTest(`
280
+ // @nudo:case "throw-obj" ()
281
+ function throwError() {
282
+ throw { code: 500, message: "Internal error" };
283
+ }
284
+ `);
285
+ expect(results[0].result).toBe("never");
286
+ });
287
+
288
+ it("try without catch", () => {
289
+ const results = runTest(`
290
+ // @nudo:case "try-finally" ()
291
+ function tryFinally() {
292
+ try {
293
+ return "success";
294
+ } finally {
295
+ // cleanup
296
+ }
297
+ }
298
+ `);
299
+ expect(results[0].result).toBe('"success"');
300
+ });
301
+
302
+ it("catch with rethrow", () => {
303
+ const results = runTest(`
304
+ // @nudo:case "catch-rethrow" ()
305
+ function catchRethrow() {
306
+ try {
307
+ throw "error";
308
+ } catch (e) {
309
+ throw e;
310
+ }
311
+ }
312
+ `);
313
+ expect(results[0].result).toBe("never");
314
+ });
315
+
316
+ it("nested try-catch-finally", () => {
317
+ const results = runTest(`
318
+ // @nudo:case "nested-tcf" ()
319
+ function nested() {
320
+ try {
321
+ try {
322
+ return "inner";
323
+ } catch (e) {
324
+ return "inner-catch";
325
+ } finally {
326
+ // inner finally
327
+ }
328
+ } catch (e) {
329
+ return "outer-catch";
330
+ }
331
+ }
332
+ `);
333
+ // Nudo unions all possible paths - correct for static analysis
334
+ expect(results[0].result).toContain('"inner"');
335
+ });
336
+ });
337
+
338
+ describe("Comparison Edge Cases", () => {
339
+ it("strict equality", () => {
340
+ const results = runTest(`
341
+ // @nudo:case "strict-eq" (1, 1)
342
+ function compare(a, b) {
343
+ return a === b;
344
+ }
345
+ `);
346
+ expect(results[0].result).toBe("true");
347
+ });
348
+
349
+ it("loose equality", () => {
350
+ const results = runTest(`
351
+ // @nudo:case "loose-eq" (1, "1")
352
+ function compare(a, b) {
353
+ return a == b;
354
+ }
355
+ `);
356
+ // Design limitation: == not implemented (returns unknown) → 已修复:
357
+ // 字面量操作数按 JS 宽松相等语义求值,1 == "1" 得 true
358
+ expect(results[0].result).toBe("true");
359
+ });
360
+
361
+ it("loose equality on literals", () => {
362
+ const results = runTest(`
363
+ // @nudo:case "num-eq" (5, 3)
364
+ // @nudo:case "str-num-eq" ("5", 5)
365
+ // @nudo:case "bool-eq" (false, 0)
366
+ // @nudo:case "null-undef-eq" (null, undefined)
367
+ function compare(a, b) {
368
+ return a == b;
369
+ }
370
+ `);
371
+ // 宽松相等字面量求值:ToNumber 强转("5"==5 → true、false==0 → true、
372
+ // null==undefined → true)
373
+ expect(results[0].result).toBe("false");
374
+ expect(results[1].result).toBe("true");
375
+ expect(results[2].result).toBe("true");
376
+ expect(results[3].result).toBe("true");
377
+ });
378
+
379
+ it("loose inequality on literals", () => {
380
+ const results = runTest(`
381
+ // @nudo:case "num-neq" (5, 3)
382
+ // @nudo:case "str-num-neq" ("5", 5)
383
+ function compare(a, b) {
384
+ return a != b;
385
+ }
386
+ `);
387
+ expect(results[0].result).toBe("true");
388
+ expect(results[1].result).toBe("false");
389
+ });
390
+
391
+ it("loose equality on non-literals degrades to boolean", () => {
392
+ const results = runTest(`
393
+ // @nudo:case "sym-eq" (T.string)
394
+ // @nudo:case "sym-neq" (T.number)
395
+ function compare(a, b) {
396
+ return a == b;
397
+ }
398
+ `);
399
+ // 非字面量操作数:静态无法定值,退化为 boolean
400
+ expect(results[0].result).toBe("boolean");
401
+ expect(results[1].result).toBe("boolean");
402
+ });
403
+
404
+ it("loose equality NaN never equals itself", () => {
405
+ const results = runTest(`
406
+ // @nudo:case "nan-eq" ()
407
+ function compare() {
408
+ return 0 / 0 == 0 / 0;
409
+ }
410
+ `);
411
+ // case 参数无法表达 NaN 字面量(parseTypeValueExpr 落到 T.unknown),
412
+ // 用 0/0(Ops.div 产出 T.literal(NaN))触发:JS 宽松相等 NaN == NaN
413
+ // 得 false
414
+ expect(results[0].result).toBe("false");
415
+ });
416
+
417
+ it("strict inequality", () => {
418
+ const results = runTest(`
419
+ // @nudo:case "strict-neq" (1, 2)
420
+ function compare(a, b) {
421
+ return a !== b;
422
+ }
423
+ `);
424
+ expect(results[0].result).toBe("true");
425
+ });
426
+
427
+ it("null comparison", () => {
428
+ const results = runTest(`
429
+ // @nudo:case "null-cmp" (null)
430
+ function compare(val) {
431
+ return val === null;
432
+ }
433
+ `);
434
+ expect(results[0].result).toBe("true");
435
+ });
436
+
437
+ it("undefined comparison", () => {
438
+ const results = runTest(`
439
+ // @nudo:case "undef-cmp" (undefined)
440
+ function compare(val) {
441
+ return val === undefined;
442
+ }
443
+ `);
444
+ expect(results[0].result).toBe("true");
445
+ });
446
+
447
+ it("typeof comparison", () => {
448
+ const results = runTest(`
449
+ // @nudo:case "typeof-cmp" (42)
450
+ function compare(val) {
451
+ return typeof val === "number";
452
+ }
453
+ `);
454
+ expect(results[0].result).toBe("true");
455
+ });
456
+ });
457
+
458
+ describe("String Edge Cases", () => {
459
+ it("empty string operations", () => {
460
+ const results = runTest(`
461
+ // @nudo:case "empty-str-ops" ("")
462
+ function process(str) {
463
+ return {
464
+ length: str.length,
465
+ upper: str.toUpperCase(),
466
+ lower: str.toLowerCase(),
467
+ trim: str.trim()
468
+ };
469
+ }
470
+ `);
471
+ expect(results[0].result).toContain("length");
472
+ expect(results[0].result).toContain("upper");
473
+ });
474
+
475
+ it("string with special chars", () => {
476
+ const results = runTest(`
477
+ // @nudo:case "special-chars" ("hello\\nworld")
478
+ function process(str) {
479
+ return str.length;
480
+ }
481
+ `);
482
+ // Nudo correctly infers the literal length
483
+ expect(results[0].result).toBe("12");
484
+ });
485
+
486
+ it("string methods on empty", () => {
487
+ const results = runTest(`
488
+ // @nudo:case "str-methods-empty" ("")
489
+ function process(str) {
490
+ return str.includes("");
491
+ }
492
+ `);
493
+ expect(results[0].result).toBe("true");
494
+ });
495
+ });
496
+
497
+ describe("Array Edge Cases", () => {
498
+ it("single element array", () => {
499
+ const results = runTest(`
500
+ // @nudo:case "single-arr" ([42])
501
+ function process(arr) {
502
+ return arr[0];
503
+ }
504
+ `);
505
+ expect(results[0].result).toBe("42");
506
+ });
507
+
508
+ it("nested empty arrays", () => {
509
+ const results = runTest(`
510
+ // @nudo:case "nested-empty" ([[], [], []])
511
+ function process(arr) {
512
+ return arr.length;
513
+ }
514
+ `);
515
+ expect(results[0].result).toBe("3");
516
+ });
517
+
518
+ it("array with mixed types", () => {
519
+ const results = runTest(`
520
+ // @nudo:case "mixed-arr" ([1, "two", true, null])
521
+ function process(arr) {
522
+ return arr.length;
523
+ }
524
+ `);
525
+ expect(results[0].result).toBe("4");
526
+ });
527
+
528
+ it("array spread", () => {
529
+ const results = runTest(`
530
+ // @nudo:case "arr-spread" ([1, 2], [3, 4])
531
+ function spread(a, b) {
532
+ return [...a, ...b];
533
+ }
534
+ `);
535
+ expect(results[0].result).toContain("[");
536
+ expect(results[0].result).toContain("1");
537
+ expect(results[0].result).toContain("4");
538
+ });
539
+
540
+ it("array destructuring with rest", () => {
541
+ const results = runTest(`
542
+ // @nudo:case "arr-rest" ([1, 2, 3, 4, 5])
543
+ function process(arr) {
544
+ const [first, ...rest] = arr;
545
+ return { first, rest };
546
+ }
547
+ `);
548
+ expect(results[0].result).toContain("first");
549
+ expect(results[0].result).toContain("rest");
550
+ });
551
+ });
552
+
553
+ describe("Object Edge Cases", () => {
554
+ it("nested object access", () => {
555
+ const results = runTest(`
556
+ // @nudo:case "nested-access" ({ a: { b: { c: 42 } } })
557
+ function access(obj) {
558
+ return obj.a.b.c;
559
+ }
560
+ `);
561
+ expect(results[0].result).toBe("42");
562
+ });
563
+
564
+ it("object with computed keys", () => {
565
+ const results = runTest(`
566
+ // @nudo:case "computed-keys" ("name", "Alice")
567
+ function create(key, value) {
568
+ return { [key]: value, [key + "Id"]: 1 };
569
+ }
570
+ `);
571
+ expect(results[0].result).toContain("name");
572
+ expect(results[0].result).toContain("Alice");
573
+ });
574
+
575
+ it("object rest", () => {
576
+ const results = runTest(`
577
+ // @nudo:case "obj-rest" ({ a: 1, b: 2, c: 3 })
578
+ function rest(obj) {
579
+ const { a, ...rest } = obj;
580
+ return rest;
581
+ }
582
+ `);
583
+ expect(results[0].result).toContain("b");
584
+ expect(results[0].result).toContain("c");
585
+ });
586
+
587
+ it("shorthand properties", () => {
588
+ const results = runTest(`
589
+ // @nudo:case "shorthand" (1, 2, 3)
590
+ function create(a, b, c) {
591
+ return { a, b, c };
592
+ }
593
+ `);
594
+ expect(results[0].result).toContain("a");
595
+ expect(results[0].result).toContain("b");
596
+ expect(results[0].result).toContain("c");
597
+ });
598
+
599
+ it("valueOf chain: object receiver without a custom valueOf returns itself", () => {
600
+ // hoek internals.valueOf pattern — the try/catch must not poison the
601
+ // passthrough with an unknown-typed catch binding.
602
+ const results = runTest(`
603
+ // @nudo:case "value-of-obj" ({ a: { b: 5 } })
604
+ function valueOf(obj) {
605
+ const objValueOf = obj.valueOf;
606
+ if (objValueOf === undefined) {
607
+ return obj;
608
+ }
609
+ try {
610
+ return objValueOf.call(obj);
611
+ } catch (err) {
612
+ return err;
613
+ }
614
+ }
615
+ `);
616
+ expect(results[0].result).toContain("a");
617
+ expect(results[0].result).toContain("b");
618
+ });
619
+
620
+ it("valueOf chain: primitive receivers unbox to their own value", () => {
621
+ const results = runTest(`
622
+ // @nudo:case "value-of-literal" (5)
623
+ function valueOfNumber(n) {
624
+ const v = n.valueOf;
625
+ return v.call(n);
626
+ }
627
+ `);
628
+ expect(results[0].result).toBe("5");
629
+ });
630
+
631
+ it("valueOf chain: null receiver throws into the catch as TypeError", () => {
632
+ const results = runTest(`
633
+ // @nudo:case "value-of-null" (null)
634
+ function valueOfNull(n) {
635
+ try {
636
+ const v = n.valueOf;
637
+ return v.call(n);
638
+ } catch (err) {
639
+ return err;
640
+ }
641
+ }
642
+ `);
643
+ expect(results[0].result).toContain("TypeError");
644
+ });
645
+ });
646
+
647
+ describe("Loop Edge Cases", () => {
648
+ it("empty loop", () => {
649
+ const results = runTest(`
650
+ // @nudo:case "empty-loop" ([])
651
+ function process(arr) {
652
+ let sum = 0;
653
+ for (const item of arr) {
654
+ sum += item;
655
+ }
656
+ return sum;
657
+ }
658
+ `);
659
+ expect(results[0].result).toBe("0");
660
+ });
661
+
662
+ it("single iteration", () => {
663
+ const results = runTest(`
664
+ // @nudo:case "single-iter" ([42])
665
+ function process(arr) {
666
+ let sum = 0;
667
+ for (const item of arr) {
668
+ sum += item;
669
+ }
670
+ return sum;
671
+ }
672
+ `);
673
+ expect(results[0].result).toBe("42");
674
+ });
675
+
676
+ it("while loop", () => {
677
+ const results = runTest(`
678
+ // @nudo:case "while-loop" (5)
679
+ function countdown(n) {
680
+ let result = 0;
681
+ while (n > 0) {
682
+ result += n;
683
+ n--;
684
+ }
685
+ return result;
686
+ }
687
+ `);
688
+ expect(results[0].result).toBe("15");
689
+ });
690
+
691
+ it("break in loop", () => {
692
+ const results = runTest(`
693
+ // @nudo:case "break-loop" ([1, 2, 3, 4, 5])
694
+ function findFirst(arr) {
695
+ for (const item of arr) {
696
+ if (item > 3) return item;
697
+ }
698
+ return undefined;
699
+ }
700
+ `);
701
+ // Nudo returns a refined type with the constraint
702
+ expect(results[0].result).toContain(">=");
703
+ });
704
+ });
705
+
706
+ describe("Function Edge Cases", () => {
707
+ it("immediately invoked function", () => {
708
+ const results = runTest(`
709
+ // @nudo:case "iife" ()
710
+ function test() {
711
+ return (function() { return 42; })();
712
+ }
713
+ `);
714
+ expect(results[0].result).toBe("42");
715
+ });
716
+
717
+ it("arrow function", () => {
718
+ const results = runTest(`
719
+ // @nudo:case "arrow" ()
720
+ function test() {
721
+ const add = (a, b) => a + b;
722
+ return add(1, 2);
723
+ }
724
+ `);
725
+ expect(results[0].result).toBe("3");
726
+ });
727
+
728
+ it("default parameters", () => {
729
+ const results = runTest(`
730
+ // @nudo:case "default-params" (1)
731
+ function test(a, b = 10) {
732
+ return a + b;
733
+ }
734
+ `);
735
+ expect(results[0].result).toBe("11");
736
+ });
737
+
738
+ it("rest parameters", () => {
739
+ const results = runTest(`
740
+ // @nudo:case "rest-params" (1, 2, 3)
741
+ function sum(...numbers) {
742
+ return numbers.reduce((a, b) => a + b, 0);
743
+ }
744
+ `);
745
+ // Design limitation: rest parameters create array, reduce on array returns unknown
746
+ expect(results[0].result).toBe("unknown");
747
+ });
748
+ });
749
+ });