@rcrsr/rill-cli 0.7.2 → 0.8.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 (57) hide show
  1. package/dist/cli-shared.d.ts.map +1 -1
  2. package/dist/cli-shared.js +3 -1
  3. package/dist/cli-shared.js.map +1 -1
  4. package/package.json +16 -1
  5. package/src/check/config.ts +0 -202
  6. package/src/check/fixer.ts +0 -174
  7. package/src/check/index.ts +0 -39
  8. package/src/check/rules/anti-patterns.ts +0 -585
  9. package/src/check/rules/closures.ts +0 -445
  10. package/src/check/rules/collections.ts +0 -437
  11. package/src/check/rules/conditionals.ts +0 -155
  12. package/src/check/rules/flow.ts +0 -262
  13. package/src/check/rules/formatting.ts +0 -811
  14. package/src/check/rules/helpers.ts +0 -89
  15. package/src/check/rules/index.ts +0 -140
  16. package/src/check/rules/loops.ts +0 -372
  17. package/src/check/rules/naming.ts +0 -242
  18. package/src/check/rules/strings.ts +0 -104
  19. package/src/check/rules/types.ts +0 -213
  20. package/src/check/types.ts +0 -163
  21. package/src/check/validator.ts +0 -136
  22. package/src/check/visitor.ts +0 -338
  23. package/src/cli-check.ts +0 -456
  24. package/src/cli-error-enrichment.ts +0 -274
  25. package/src/cli-error-formatter.ts +0 -313
  26. package/src/cli-eval.ts +0 -145
  27. package/src/cli-exec.ts +0 -408
  28. package/src/cli-explain.ts +0 -76
  29. package/src/cli-lsp-diagnostic.ts +0 -132
  30. package/src/cli-module-loader.ts +0 -101
  31. package/src/cli-shared.ts +0 -187
  32. package/tests/check/cli-check.test.ts +0 -189
  33. package/tests/check/config.test.ts +0 -350
  34. package/tests/check/fixer.test.ts +0 -373
  35. package/tests/check/format-diagnostics.test.ts +0 -327
  36. package/tests/check/rules/anti-patterns.test.ts +0 -467
  37. package/tests/check/rules/closures.test.ts +0 -192
  38. package/tests/check/rules/collections.test.ts +0 -380
  39. package/tests/check/rules/conditionals.test.ts +0 -185
  40. package/tests/check/rules/flow.test.ts +0 -250
  41. package/tests/check/rules/formatting.test.ts +0 -755
  42. package/tests/check/rules/loops.test.ts +0 -334
  43. package/tests/check/rules/naming.test.ts +0 -336
  44. package/tests/check/rules/strings.test.ts +0 -129
  45. package/tests/check/rules/types.test.ts +0 -233
  46. package/tests/check/validator.test.ts +0 -444
  47. package/tests/check/visitor.test.ts +0 -171
  48. package/tests/cli/check.test.ts +0 -801
  49. package/tests/cli/error-enrichment.test.ts +0 -510
  50. package/tests/cli/error-formatter.test.ts +0 -631
  51. package/tests/cli/eval.test.ts +0 -85
  52. package/tests/cli/exec.test.ts +0 -537
  53. package/tests/cli-explain.test.ts +0 -249
  54. package/tests/cli-lsp-diagnostic.test.ts +0 -202
  55. package/tests/cli-shared.test.ts +0 -439
  56. package/tsconfig.json +0 -9
  57. package/tsconfig.tsbuildinfo +0 -1
@@ -1,631 +0,0 @@
1
- /**
2
- * Tests for CLI Error Formatter
3
- * Covers: IR-5, IR-7, EC-5, EC-8, IC-2
4
- */
5
-
6
- import { describe, it, expect } from 'vitest';
7
- import {
8
- formatError,
9
- renderCaretUnderline,
10
- type EnrichedError,
11
- type FormatOptions,
12
- type CallFrame,
13
- } from '../../src/cli-error-formatter.js';
14
- import type {
15
- SourceSpan,
16
- SourceLocation,
17
- } from '@rcrsr/rill';
18
- import type { SourceSnippet } from '../../src/cli-error-enrichment.js';
19
-
20
- describe('formatError', () => {
21
- describe('IR-5: Human format with header and location', () => {
22
- it('formats error with header and location', () => {
23
- const error: EnrichedError = {
24
- errorId: 'RILL-R005',
25
- message: 'Variable foo is not defined',
26
- span: {
27
- start: { line: 5, column: 10, offset: 50 },
28
- end: { line: 5, column: 13, offset: 53 },
29
- },
30
- };
31
-
32
- const options: FormatOptions = {
33
- format: 'human',
34
- verbose: false,
35
- includeCallStack: false,
36
- maxCallStackDepth: 10,
37
- };
38
-
39
- const result = formatError(error, options);
40
-
41
- expect(result).toContain('error[RILL-R005]: Variable foo is not defined');
42
- expect(result).toContain(' --> 5:10');
43
- });
44
-
45
- it('formats error with source snippet and caret underline', () => {
46
- const error: EnrichedError = {
47
- errorId: 'RILL-R005',
48
- message: 'Variable foo is not defined',
49
- span: {
50
- start: { line: 3, column: 2, offset: 20 },
51
- end: { line: 3, column: 6, offset: 24 },
52
- },
53
- sourceSnippet: {
54
- lines: [
55
- { lineNumber: 1, content: '"start" => $begin', isErrorLine: false },
56
- {
57
- lineNumber: 2,
58
- content: '$begin -> .upper => $upper',
59
- isErrorLine: false,
60
- },
61
- { lineNumber: 3, content: '$foo -> .len', isErrorLine: true },
62
- { lineNumber: 4, content: 'end', isErrorLine: false },
63
- ],
64
- highlightSpan: {
65
- start: { line: 3, column: 2, offset: 20 },
66
- end: { line: 3, column: 6, offset: 24 },
67
- },
68
- },
69
- };
70
-
71
- const options: FormatOptions = {
72
- format: 'human',
73
- verbose: false,
74
- includeCallStack: false,
75
- maxCallStackDepth: 10,
76
- };
77
-
78
- const result = formatError(error, options);
79
-
80
- expect(result).toContain('error[RILL-R005]');
81
- expect(result).toContain(' --> 3:2');
82
- expect(result).toContain(' |');
83
- expect(result).toContain(' 1 | "start" => $begin');
84
- expect(result).toContain(' 2 | $begin -> .upper => $upper');
85
- expect(result).toContain(' 3 | $foo -> .len');
86
- expect(result).toContain(' | ^^^^');
87
- expect(result).toContain(' 4 | end');
88
- });
89
-
90
- it('formats error with suggestions', () => {
91
- const error: EnrichedError = {
92
- errorId: 'RILL-R005',
93
- message: 'Variable foo is not defined',
94
- span: {
95
- start: { line: 3, column: 0, offset: 18 },
96
- end: { line: 3, column: 4, offset: 22 },
97
- },
98
- suggestions: ['Did you mean `$begin`?', 'Try declaring it first'],
99
- };
100
-
101
- const options: FormatOptions = {
102
- format: 'human',
103
- verbose: false,
104
- includeCallStack: false,
105
- maxCallStackDepth: 10,
106
- };
107
-
108
- const result = formatError(error, options);
109
-
110
- expect(result).toContain(' = help: Did you mean `$begin`?');
111
- expect(result).toContain(' = help: Try declaring it first');
112
- });
113
-
114
- it('includes help URL when verbose', () => {
115
- const error: EnrichedError = {
116
- errorId: 'RILL-R005',
117
- message: 'Variable foo is not defined',
118
- helpUrl: 'https://example.com/errors/R005',
119
- };
120
-
121
- const options: FormatOptions = {
122
- format: 'human',
123
- verbose: true,
124
- includeCallStack: false,
125
- maxCallStackDepth: 10,
126
- };
127
-
128
- const result = formatError(error, options);
129
-
130
- expect(result).toContain(' = see: https://example.com/errors/R005');
131
- });
132
-
133
- it('excludes help URL when not verbose', () => {
134
- const error: EnrichedError = {
135
- errorId: 'RILL-R005',
136
- message: 'Variable foo is not defined',
137
- helpUrl: 'https://example.com/errors/R005',
138
- };
139
-
140
- const options: FormatOptions = {
141
- format: 'human',
142
- verbose: false,
143
- includeCallStack: false,
144
- maxCallStackDepth: 10,
145
- };
146
-
147
- const result = formatError(error, options);
148
-
149
- expect(result).not.toContain('https://example.com/errors/R005');
150
- });
151
-
152
- it('formats error with call stack when enabled', () => {
153
- const callStack: CallFrame[] = [
154
- {
155
- location: {
156
- start: { line: 10, column: 5, offset: 100 },
157
- end: { line: 10, column: 15, offset: 110 },
158
- },
159
- functionName: 'myFunction',
160
- context: 'in each body',
161
- },
162
- {
163
- location: {
164
- start: { line: 5, column: 0, offset: 50 },
165
- end: { line: 5, column: 10, offset: 60 },
166
- },
167
- functionName: 'outer',
168
- },
169
- ];
170
-
171
- const error: EnrichedError = {
172
- errorId: 'RILL-R001',
173
- message: 'Runtime error occurred',
174
- callStack,
175
- };
176
-
177
- const options: FormatOptions = {
178
- format: 'human',
179
- verbose: false,
180
- includeCallStack: true,
181
- maxCallStackDepth: 10,
182
- };
183
-
184
- const result = formatError(error, options);
185
-
186
- expect(result).toContain('Call stack:');
187
- expect(result).toContain(' 1. myFunction (in each body) at 10:5');
188
- expect(result).toContain(' 2. outer at 5:0');
189
- });
190
-
191
- it('limits call stack depth', () => {
192
- const callStack: CallFrame[] = [
193
- {
194
- location: {
195
- start: { line: 1, column: 0, offset: 0 },
196
- end: { line: 1, column: 5, offset: 5 },
197
- },
198
- functionName: 'fn1',
199
- },
200
- {
201
- location: {
202
- start: { line: 2, column: 0, offset: 10 },
203
- end: { line: 2, column: 5, offset: 15 },
204
- },
205
- functionName: 'fn2',
206
- },
207
- {
208
- location: {
209
- start: { line: 3, column: 0, offset: 20 },
210
- end: { line: 3, column: 5, offset: 25 },
211
- },
212
- functionName: 'fn3',
213
- },
214
- ];
215
-
216
- const error: EnrichedError = {
217
- errorId: 'RILL-R001',
218
- message: 'Runtime error',
219
- callStack,
220
- };
221
-
222
- const options: FormatOptions = {
223
- format: 'human',
224
- verbose: false,
225
- includeCallStack: true,
226
- maxCallStackDepth: 2,
227
- };
228
-
229
- const result = formatError(error, options);
230
-
231
- expect(result).toContain(' 1. fn1');
232
- expect(result).toContain(' 2. fn2');
233
- expect(result).toContain(' ... 1 more frames');
234
- expect(result).not.toContain('fn3');
235
- });
236
- });
237
-
238
- describe('IR-5: JSON format with LSP structure', () => {
239
- it('formats error as JSON with LSP diagnostic structure', () => {
240
- const error: EnrichedError = {
241
- errorId: 'RILL-R005',
242
- message: 'Variable foo is not defined',
243
- span: {
244
- start: { line: 5, column: 10, offset: 50 },
245
- end: { line: 5, column: 13, offset: 53 },
246
- },
247
- suggestions: ['Did you mean `$begin`?'],
248
- };
249
-
250
- const options: FormatOptions = {
251
- format: 'json',
252
- verbose: false,
253
- includeCallStack: false,
254
- maxCallStackDepth: 10,
255
- };
256
-
257
- const result = formatError(error, options);
258
- const diagnostic = JSON.parse(result);
259
-
260
- expect(diagnostic.errorId).toBe('RILL-R005');
261
- expect(diagnostic.severity).toBe(1); // Error
262
- expect(diagnostic.message).toBe('Variable foo is not defined');
263
- expect(diagnostic.source).toBe('rill');
264
- expect(diagnostic.code).toBe('RILL-R005');
265
- expect(diagnostic.range).toEqual({
266
- start: { line: 4, character: 10 }, // LSP uses 0-based lines
267
- end: { line: 4, character: 13 },
268
- });
269
- expect(diagnostic.suggestions).toEqual(['Did you mean `$begin`?']);
270
- });
271
-
272
- it('includes call stack in JSON format when enabled', () => {
273
- const callStack: CallFrame[] = [
274
- {
275
- location: {
276
- start: { line: 10, column: 5, offset: 100 },
277
- end: { line: 10, column: 15, offset: 110 },
278
- },
279
- functionName: 'myFunction',
280
- context: 'in each body',
281
- },
282
- ];
283
-
284
- const error: EnrichedError = {
285
- errorId: 'RILL-R001',
286
- message: 'Runtime error',
287
- callStack,
288
- };
289
-
290
- const options: FormatOptions = {
291
- format: 'json',
292
- verbose: false,
293
- includeCallStack: true,
294
- maxCallStackDepth: 10,
295
- };
296
-
297
- const result = formatError(error, options);
298
- const diagnostic = JSON.parse(result);
299
-
300
- expect(diagnostic.callStack).toHaveLength(1);
301
- expect(diagnostic.callStack[0]).toEqual({
302
- location: {
303
- start: { line: 9, character: 5 }, // 0-based
304
- end: { line: 9, character: 15 },
305
- },
306
- functionName: 'myFunction',
307
- context: 'in each body',
308
- });
309
- });
310
-
311
- it('excludes call stack when not enabled', () => {
312
- const callStack: CallFrame[] = [
313
- {
314
- location: {
315
- start: { line: 10, column: 5, offset: 100 },
316
- end: { line: 10, column: 15, offset: 110 },
317
- },
318
- },
319
- ];
320
-
321
- const error: EnrichedError = {
322
- errorId: 'RILL-R001',
323
- message: 'Runtime error',
324
- callStack,
325
- };
326
-
327
- const options: FormatOptions = {
328
- format: 'json',
329
- verbose: false,
330
- includeCallStack: false,
331
- maxCallStackDepth: 10,
332
- };
333
-
334
- const result = formatError(error, options);
335
- const diagnostic = JSON.parse(result);
336
-
337
- expect(diagnostic.callStack).toBeUndefined();
338
- });
339
-
340
- it('includes help URL in JSON when verbose', () => {
341
- const error: EnrichedError = {
342
- errorId: 'RILL-R005',
343
- message: 'Variable foo is not defined',
344
- helpUrl: 'https://example.com/errors/R005',
345
- };
346
-
347
- const options: FormatOptions = {
348
- format: 'json',
349
- verbose: true,
350
- includeCallStack: false,
351
- maxCallStackDepth: 10,
352
- };
353
-
354
- const result = formatError(error, options);
355
- const diagnostic = JSON.parse(result);
356
-
357
- expect(diagnostic.helpUrl).toBe('https://example.com/errors/R005');
358
- });
359
- });
360
-
361
- describe('IR-5: Compact format single line', () => {
362
- it('formats error as single line', () => {
363
- const error: EnrichedError = {
364
- errorId: 'RILL-R005',
365
- message: 'Variable foo is not defined',
366
- span: {
367
- start: { line: 5, column: 10, offset: 50 },
368
- end: { line: 5, column: 13, offset: 53 },
369
- },
370
- };
371
-
372
- const options: FormatOptions = {
373
- format: 'compact',
374
- verbose: false,
375
- includeCallStack: false,
376
- maxCallStackDepth: 10,
377
- };
378
-
379
- const result = formatError(error, options);
380
-
381
- expect(result).toBe('[RILL-R005] Variable foo is not defined at 5:10');
382
- expect(result).not.toContain('\n');
383
- });
384
-
385
- it('includes first suggestion as hint', () => {
386
- const error: EnrichedError = {
387
- errorId: 'RILL-R005',
388
- message: 'Variable foo is not defined',
389
- suggestions: ['Did you mean `$begin`?', 'Another suggestion'],
390
- };
391
-
392
- const options: FormatOptions = {
393
- format: 'compact',
394
- verbose: false,
395
- includeCallStack: false,
396
- maxCallStackDepth: 10,
397
- };
398
-
399
- const result = formatError(error, options);
400
-
401
- expect(result).toBe(
402
- '[RILL-R005] Variable foo is not defined (hint: Did you mean `$begin`?)'
403
- );
404
- });
405
-
406
- it('formats without location when span absent', () => {
407
- const error: EnrichedError = {
408
- errorId: 'RILL-R001',
409
- message: 'Runtime error',
410
- };
411
-
412
- const options: FormatOptions = {
413
- format: 'compact',
414
- verbose: false,
415
- includeCallStack: false,
416
- maxCallStackDepth: 10,
417
- };
418
-
419
- const result = formatError(error, options);
420
-
421
- expect(result).toBe('[RILL-R001] Runtime error');
422
- });
423
- });
424
-
425
- describe('EC-5: Unknown format throws TypeError', () => {
426
- it('throws TypeError for unknown format', () => {
427
- const error: EnrichedError = {
428
- errorId: 'RILL-R001',
429
- message: 'Error message',
430
- };
431
-
432
- const options: FormatOptions = {
433
- format: 'xml' as 'human', // Force invalid format
434
- verbose: false,
435
- includeCallStack: false,
436
- maxCallStackDepth: 10,
437
- };
438
-
439
- expect(() => formatError(error, options)).toThrow(TypeError);
440
- expect(() => formatError(error, options)).toThrow('Unknown format: xml');
441
- });
442
- });
443
- });
444
-
445
- describe('renderCaretUnderline', () => {
446
- describe('IR-7: Single char shows ^', () => {
447
- it('renders single caret for single character span', () => {
448
- const span: SourceSpan = {
449
- start: { line: 1, column: 5, offset: 5 },
450
- end: { line: 1, column: 6, offset: 6 },
451
- };
452
- const lineContent = 'hello world';
453
-
454
- const result = renderCaretUnderline(span, lineContent);
455
-
456
- expect(result).toBe(' ^');
457
- });
458
- });
459
-
460
- describe('IR-7: Multi-char shows ^^^^^', () => {
461
- it('renders multiple carets for multi-character span', () => {
462
- const span: SourceSpan = {
463
- start: { line: 1, column: 2, offset: 2 },
464
- end: { line: 1, column: 6, offset: 6 },
465
- };
466
- const lineContent = '$foo -> .len';
467
-
468
- const result = renderCaretUnderline(span, lineContent);
469
-
470
- expect(result).toBe(' ^^^^'); // 2 spaces + 4 carets
471
- });
472
-
473
- it('handles zero-width span as single caret', () => {
474
- const span: SourceSpan = {
475
- start: { line: 1, column: 3, offset: 3 },
476
- end: { line: 1, column: 3, offset: 3 },
477
- };
478
- const lineContent = 'hello';
479
-
480
- const result = renderCaretUnderline(span, lineContent);
481
-
482
- expect(result).toBe(' ^'); // 3 spaces + 1 caret (minimum)
483
- });
484
- });
485
-
486
- describe('IR-7: Multi-line span continues on first line', () => {
487
- it('renders carets to end of line for multi-line span', () => {
488
- const span: SourceSpan = {
489
- start: { line: 1, column: 6, offset: 6 },
490
- end: { line: 3, column: 2, offset: 25 },
491
- };
492
- const lineContent = 'hello world';
493
-
494
- const result = renderCaretUnderline(span, lineContent);
495
-
496
- expect(result).toBe(' ^^^^^'); // 6 spaces + 5 carets (from col 6 to end)
497
- });
498
- });
499
-
500
- describe('EC-8: Invalid span throws RangeError', () => {
501
- it('throws when start line after end line', () => {
502
- const span: SourceSpan = {
503
- start: { line: 5, column: 0, offset: 50 },
504
- end: { line: 3, column: 0, offset: 30 },
505
- };
506
- const lineContent = 'content';
507
-
508
- expect(() => renderCaretUnderline(span, lineContent)).toThrow(RangeError);
509
- expect(() => renderCaretUnderline(span, lineContent)).toThrow(
510
- 'Span start must precede end'
511
- );
512
- });
513
-
514
- it('throws when start column after end column on same line', () => {
515
- const span: SourceSpan = {
516
- start: { line: 1, column: 10, offset: 10 },
517
- end: { line: 1, column: 5, offset: 5 },
518
- };
519
- const lineContent = 'hello world';
520
-
521
- expect(() => renderCaretUnderline(span, lineContent)).toThrow(RangeError);
522
- expect(() => renderCaretUnderline(span, lineContent)).toThrow(
523
- 'Span start must precede end'
524
- );
525
- });
526
- });
527
-
528
- describe('AC-20: Error at final character renders correctly', () => {
529
- it('renders caret at last character position', () => {
530
- const lineContent = 'hello world';
531
- const lastCharColumn = lineContent.length - 1; // Column of 'd'
532
-
533
- const span: SourceSpan = {
534
- start: { line: 1, column: lastCharColumn, offset: lastCharColumn },
535
- end: {
536
- line: 1,
537
- column: lineContent.length,
538
- offset: lineContent.length,
539
- },
540
- };
541
-
542
- const result = renderCaretUnderline(span, lineContent);
543
-
544
- expect(result).toBe(' ^'); // 10 spaces + 1 caret
545
- expect(result.length).toBe(lineContent.length); // Underline should align
546
- });
547
-
548
- it('renders caret at very end of line (past last char)', () => {
549
- const lineContent = 'test';
550
- const span: SourceSpan = {
551
- start: { line: 1, column: 4, offset: 4 }, // After 't' (column 4)
552
- end: { line: 1, column: 4, offset: 4 },
553
- };
554
-
555
- const result = renderCaretUnderline(span, lineContent);
556
-
557
- expect(result).toBe(' ^'); // 4 spaces + 1 caret
558
- });
559
- });
560
-
561
- describe('Edge cases', () => {
562
- it('handles span at start of line', () => {
563
- const span: SourceSpan = {
564
- start: { line: 1, column: 0, offset: 0 },
565
- end: { line: 1, column: 5, offset: 5 },
566
- };
567
- const lineContent = 'hello';
568
-
569
- const result = renderCaretUnderline(span, lineContent);
570
-
571
- expect(result).toBe('^^^^^'); // No padding, 5 carets
572
- });
573
-
574
- it('handles empty line content', () => {
575
- const span: SourceSpan = {
576
- start: { line: 1, column: 0, offset: 0 },
577
- end: { line: 1, column: 0, offset: 0 },
578
- };
579
- const lineContent = '';
580
-
581
- const result = renderCaretUnderline(span, lineContent);
582
-
583
- expect(result).toBe('^'); // Single caret (minimum)
584
- });
585
-
586
- it('handles span extending beyond line content', () => {
587
- const span: SourceSpan = {
588
- start: { line: 1, column: 3, offset: 3 },
589
- end: { line: 1, column: 10, offset: 10 },
590
- };
591
- const lineContent = 'hi'; // Only 2 chars, but span goes to col 10
592
-
593
- const result = renderCaretUnderline(span, lineContent);
594
-
595
- expect(result).toBe(' ^^^^^^^'); // 3 spaces + 7 carets
596
- });
597
- });
598
- });
599
-
600
- describe('IC-2: Type definitions present', () => {
601
- it('exports EnrichedError type', () => {
602
- const error: EnrichedError = {
603
- errorId: 'RILL-R001',
604
- message: 'Test',
605
- };
606
-
607
- expect(error.errorId).toBe('RILL-R001');
608
- });
609
-
610
- it('exports FormatOptions type', () => {
611
- const options: FormatOptions = {
612
- format: 'human',
613
- verbose: false,
614
- includeCallStack: false,
615
- maxCallStackDepth: 10,
616
- };
617
-
618
- expect(options.format).toBe('human');
619
- });
620
-
621
- it('exports CallFrame type', () => {
622
- const frame: CallFrame = {
623
- location: {
624
- start: { line: 1, column: 0, offset: 0 },
625
- end: { line: 1, column: 5, offset: 5 },
626
- },
627
- };
628
-
629
- expect(frame.location.start.line).toBe(1);
630
- });
631
- });
@@ -1,85 +0,0 @@
1
- /**
2
- * Rill CLI Tests: rill-eval command
3
- */
4
-
5
- import { describe, expect, it } from 'vitest';
6
- import {
7
- ParseError,
8
- RuntimeError,
9
- callable,
10
- isCallable,
11
- } from '@rcrsr/rill';
12
- import { formatOutput } from '../../src/cli-shared.js';
13
- import { evaluateExpression } from '../../src/cli-eval.js';
14
-
15
- describe('rill-eval', () => {
16
- describe('evaluateExpression', () => {
17
- it('evaluates string methods', async () => {
18
- expect((await evaluateExpression('"hello".len')).value).toBe(5);
19
- expect((await evaluateExpression('"hello".upper')).value).toBe('HELLO');
20
- expect((await evaluateExpression('" hi ".trim')).value).toBe('hi');
21
- });
22
-
23
- it('evaluates arithmetic', async () => {
24
- expect((await evaluateExpression('5 + 3')).value).toBe(8);
25
- expect((await evaluateExpression('10 - 4')).value).toBe(6);
26
- expect((await evaluateExpression('6 * 7')).value).toBe(42);
27
- });
28
-
29
- it('evaluates pipes', async () => {
30
- expect((await evaluateExpression('"hello" -> .upper')).value).toBe(
31
- 'HELLO'
32
- );
33
- });
34
-
35
- it('evaluates collections', async () => {
36
- expect((await evaluateExpression('[1, 2, 3] -> .len')).value).toBe(3);
37
- expect(
38
- (await evaluateExpression('[1, 2, 3] -> map |x|($x * 2)')).value
39
- ).toEqual([2, 4, 6]);
40
- expect((await evaluateExpression('[a: 1].a')).value).toBe(1);
41
- });
42
-
43
- it('evaluates closures', async () => {
44
- const result = await evaluateExpression('|x| { $x }');
45
- expect(isCallable(result.value)).toBe(true);
46
- expect(formatOutput(result.value)).toBe('[closure]');
47
- });
48
-
49
- it('handles empty values', async () => {
50
- expect((await evaluateExpression('""')).value).toBe('');
51
- expect((await evaluateExpression('[]')).value).toEqual([]);
52
- expect((await evaluateExpression('0')).value).toBe(0);
53
- });
54
-
55
- it('throws parse errors', async () => {
56
- await expect(evaluateExpression('{')).rejects.toThrow(ParseError);
57
- await expect(evaluateExpression('|x| x }')).rejects.toThrow(ParseError);
58
- });
59
-
60
- it('throws runtime errors', async () => {
61
- await expect(evaluateExpression('$undefined')).rejects.toThrow(
62
- RuntimeError
63
- );
64
- await expect(evaluateExpression('"string" + 5')).rejects.toThrow(
65
- RuntimeError
66
- );
67
- });
68
-
69
- it('preserves error details', async () => {
70
- try {
71
- await evaluateExpression('$missing');
72
- } catch (err) {
73
- expect(err).toBeInstanceOf(RuntimeError);
74
- expect((err as RuntimeError).errorId).toBe('RILL-R005');
75
- expect((err as RuntimeError).location?.line).toBe(1);
76
- }
77
- });
78
- });
79
-
80
- describe('formatOutput for eval results', () => {
81
- it('formats closures from expressions', () => {
82
- expect(formatOutput(callable(() => 'x'))).toBe('[closure]');
83
- });
84
- });
85
- });