@vibe-lang/runtime 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/package.json +46 -0
  2. package/src/ast/index.ts +375 -0
  3. package/src/ast.ts +2 -0
  4. package/src/debug/advanced-features.ts +482 -0
  5. package/src/debug/bun-inspector.ts +424 -0
  6. package/src/debug/handoff-manager.ts +283 -0
  7. package/src/debug/index.ts +150 -0
  8. package/src/debug/runner.ts +365 -0
  9. package/src/debug/server.ts +565 -0
  10. package/src/debug/stack-merger.ts +267 -0
  11. package/src/debug/state.ts +581 -0
  12. package/src/debug/test/advanced-features.test.ts +300 -0
  13. package/src/debug/test/e2e.test.ts +218 -0
  14. package/src/debug/test/handoff-manager.test.ts +256 -0
  15. package/src/debug/test/runner.test.ts +256 -0
  16. package/src/debug/test/stack-merger.test.ts +163 -0
  17. package/src/debug/test/state.test.ts +400 -0
  18. package/src/debug/test/ts-debug-integration.test.ts +374 -0
  19. package/src/debug/test/ts-import-tracker.test.ts +125 -0
  20. package/src/debug/test/ts-source-map.test.ts +169 -0
  21. package/src/debug/ts-import-tracker.ts +151 -0
  22. package/src/debug/ts-source-map.ts +171 -0
  23. package/src/errors/index.ts +124 -0
  24. package/src/index.ts +358 -0
  25. package/src/lexer/index.ts +348 -0
  26. package/src/lexer.ts +2 -0
  27. package/src/parser/index.ts +792 -0
  28. package/src/parser/parse.ts +45 -0
  29. package/src/parser/test/async.test.ts +248 -0
  30. package/src/parser/test/destructuring.test.ts +167 -0
  31. package/src/parser/test/do-expression.test.ts +486 -0
  32. package/src/parser/test/errors/do-expression.test.ts +95 -0
  33. package/src/parser/test/errors/error-locations.test.ts +230 -0
  34. package/src/parser/test/errors/invalid-expressions.test.ts +144 -0
  35. package/src/parser/test/errors/missing-tokens.test.ts +126 -0
  36. package/src/parser/test/errors/model-declaration.test.ts +185 -0
  37. package/src/parser/test/errors/nested-blocks.test.ts +226 -0
  38. package/src/parser/test/errors/unclosed-delimiters.test.ts +122 -0
  39. package/src/parser/test/errors/unexpected-tokens.test.ts +120 -0
  40. package/src/parser/test/import-export.test.ts +143 -0
  41. package/src/parser/test/literals.test.ts +404 -0
  42. package/src/parser/test/model-declaration.test.ts +161 -0
  43. package/src/parser/test/nested-blocks.test.ts +402 -0
  44. package/src/parser/test/parser.test.ts +743 -0
  45. package/src/parser/test/private.test.ts +136 -0
  46. package/src/parser/test/template-literal.test.ts +127 -0
  47. package/src/parser/test/tool-declaration.test.ts +302 -0
  48. package/src/parser/test/ts-block.test.ts +252 -0
  49. package/src/parser/test/type-annotations.test.ts +254 -0
  50. package/src/parser/visitor/helpers.ts +330 -0
  51. package/src/parser/visitor.ts +794 -0
  52. package/src/parser.ts +2 -0
  53. package/src/runtime/ai/cache-chunking.test.ts +69 -0
  54. package/src/runtime/ai/cache-chunking.ts +73 -0
  55. package/src/runtime/ai/client.ts +109 -0
  56. package/src/runtime/ai/context.ts +168 -0
  57. package/src/runtime/ai/formatters.ts +316 -0
  58. package/src/runtime/ai/index.ts +38 -0
  59. package/src/runtime/ai/language-ref.ts +38 -0
  60. package/src/runtime/ai/providers/anthropic.ts +253 -0
  61. package/src/runtime/ai/providers/google.ts +201 -0
  62. package/src/runtime/ai/providers/openai.ts +156 -0
  63. package/src/runtime/ai/retry.ts +100 -0
  64. package/src/runtime/ai/return-tools.ts +301 -0
  65. package/src/runtime/ai/test/client.test.ts +83 -0
  66. package/src/runtime/ai/test/formatters.test.ts +485 -0
  67. package/src/runtime/ai/test/retry.test.ts +137 -0
  68. package/src/runtime/ai/test/return-tools.test.ts +450 -0
  69. package/src/runtime/ai/test/tool-loop.test.ts +319 -0
  70. package/src/runtime/ai/test/tool-schema.test.ts +241 -0
  71. package/src/runtime/ai/tool-loop.ts +203 -0
  72. package/src/runtime/ai/tool-schema.ts +151 -0
  73. package/src/runtime/ai/types.ts +113 -0
  74. package/src/runtime/ai-logger.ts +255 -0
  75. package/src/runtime/ai-provider.ts +347 -0
  76. package/src/runtime/async/dependencies.ts +276 -0
  77. package/src/runtime/async/executor.ts +293 -0
  78. package/src/runtime/async/index.ts +43 -0
  79. package/src/runtime/async/scheduling.ts +163 -0
  80. package/src/runtime/async/test/dependencies.test.ts +284 -0
  81. package/src/runtime/async/test/executor.test.ts +388 -0
  82. package/src/runtime/context.ts +357 -0
  83. package/src/runtime/exec/ai.ts +139 -0
  84. package/src/runtime/exec/expressions.ts +475 -0
  85. package/src/runtime/exec/frames.ts +26 -0
  86. package/src/runtime/exec/functions.ts +305 -0
  87. package/src/runtime/exec/interpolation.ts +312 -0
  88. package/src/runtime/exec/statements.ts +604 -0
  89. package/src/runtime/exec/tools.ts +129 -0
  90. package/src/runtime/exec/typescript.ts +215 -0
  91. package/src/runtime/exec/variables.ts +279 -0
  92. package/src/runtime/index.ts +975 -0
  93. package/src/runtime/modules.ts +452 -0
  94. package/src/runtime/serialize.ts +103 -0
  95. package/src/runtime/state.ts +489 -0
  96. package/src/runtime/stdlib/core.ts +45 -0
  97. package/src/runtime/stdlib/directory.test.ts +156 -0
  98. package/src/runtime/stdlib/edit.test.ts +154 -0
  99. package/src/runtime/stdlib/fastEdit.test.ts +201 -0
  100. package/src/runtime/stdlib/glob.test.ts +106 -0
  101. package/src/runtime/stdlib/grep.test.ts +144 -0
  102. package/src/runtime/stdlib/index.ts +16 -0
  103. package/src/runtime/stdlib/readFile.test.ts +123 -0
  104. package/src/runtime/stdlib/tools/index.ts +707 -0
  105. package/src/runtime/stdlib/writeFile.test.ts +157 -0
  106. package/src/runtime/step.ts +969 -0
  107. package/src/runtime/test/ai-context.test.ts +1086 -0
  108. package/src/runtime/test/ai-result-object.test.ts +419 -0
  109. package/src/runtime/test/ai-tool-flow.test.ts +859 -0
  110. package/src/runtime/test/async-execution-order.test.ts +618 -0
  111. package/src/runtime/test/async-execution.test.ts +344 -0
  112. package/src/runtime/test/async-nested.test.ts +660 -0
  113. package/src/runtime/test/async-parallel-timing.test.ts +546 -0
  114. package/src/runtime/test/basic1.test.ts +154 -0
  115. package/src/runtime/test/binary-operators.test.ts +431 -0
  116. package/src/runtime/test/break-statement.test.ts +257 -0
  117. package/src/runtime/test/context-modes.test.ts +650 -0
  118. package/src/runtime/test/context.test.ts +466 -0
  119. package/src/runtime/test/core-functions.test.ts +228 -0
  120. package/src/runtime/test/e2e.test.ts +88 -0
  121. package/src/runtime/test/error-locations/error-locations.test.ts +80 -0
  122. package/src/runtime/test/error-locations/main-error.vibe +4 -0
  123. package/src/runtime/test/error-locations/main-import-error.vibe +3 -0
  124. package/src/runtime/test/error-locations/utils/helper.vibe +5 -0
  125. package/src/runtime/test/for-in.test.ts +312 -0
  126. package/src/runtime/test/helpers.ts +69 -0
  127. package/src/runtime/test/imports.test.ts +334 -0
  128. package/src/runtime/test/json-expressions.test.ts +232 -0
  129. package/src/runtime/test/literals.test.ts +372 -0
  130. package/src/runtime/test/logical-indexing.test.ts +478 -0
  131. package/src/runtime/test/member-methods.test.ts +324 -0
  132. package/src/runtime/test/model-config.test.ts +338 -0
  133. package/src/runtime/test/null-handling.test.ts +342 -0
  134. package/src/runtime/test/private-visibility.test.ts +332 -0
  135. package/src/runtime/test/runtime-state.test.ts +514 -0
  136. package/src/runtime/test/scoping.test.ts +370 -0
  137. package/src/runtime/test/string-interpolation.test.ts +354 -0
  138. package/src/runtime/test/template-literal.test.ts +181 -0
  139. package/src/runtime/test/tool-execution.test.ts +467 -0
  140. package/src/runtime/test/tool-schema-generation.test.ts +477 -0
  141. package/src/runtime/test/tostring.test.ts +210 -0
  142. package/src/runtime/test/ts-block.test.ts +594 -0
  143. package/src/runtime/test/ts-error-location.test.ts +231 -0
  144. package/src/runtime/test/types.test.ts +732 -0
  145. package/src/runtime/test/verbose-logger.test.ts +710 -0
  146. package/src/runtime/test/vibe-expression.test.ts +54 -0
  147. package/src/runtime/test/vibe-value-errors.test.ts +541 -0
  148. package/src/runtime/test/while.test.ts +232 -0
  149. package/src/runtime/tools/builtin.ts +30 -0
  150. package/src/runtime/tools/directory-tools.ts +70 -0
  151. package/src/runtime/tools/file-tools.ts +228 -0
  152. package/src/runtime/tools/index.ts +5 -0
  153. package/src/runtime/tools/registry.ts +48 -0
  154. package/src/runtime/tools/search-tools.ts +134 -0
  155. package/src/runtime/tools/security.ts +36 -0
  156. package/src/runtime/tools/system-tools.ts +312 -0
  157. package/src/runtime/tools/test/fixtures/base-types.ts +40 -0
  158. package/src/runtime/tools/test/fixtures/test-types.ts +132 -0
  159. package/src/runtime/tools/test/registry.test.ts +713 -0
  160. package/src/runtime/tools/test/security.test.ts +86 -0
  161. package/src/runtime/tools/test/system-tools.test.ts +679 -0
  162. package/src/runtime/tools/test/ts-schema.test.ts +357 -0
  163. package/src/runtime/tools/ts-schema.ts +341 -0
  164. package/src/runtime/tools/types.ts +89 -0
  165. package/src/runtime/tools/utility-tools.ts +198 -0
  166. package/src/runtime/ts-eval.ts +126 -0
  167. package/src/runtime/types.ts +797 -0
  168. package/src/runtime/validation.ts +160 -0
  169. package/src/runtime/verbose-logger.ts +459 -0
  170. package/src/runtime.ts +2 -0
  171. package/src/semantic/analyzer-context.ts +62 -0
  172. package/src/semantic/analyzer-validators.ts +575 -0
  173. package/src/semantic/analyzer-visitors.ts +534 -0
  174. package/src/semantic/analyzer.ts +83 -0
  175. package/src/semantic/index.ts +11 -0
  176. package/src/semantic/symbol-table.ts +58 -0
  177. package/src/semantic/test/async-validation.test.ts +301 -0
  178. package/src/semantic/test/compress-validation.test.ts +179 -0
  179. package/src/semantic/test/const-reassignment.test.ts +111 -0
  180. package/src/semantic/test/control-flow.test.ts +346 -0
  181. package/src/semantic/test/destructuring.test.ts +185 -0
  182. package/src/semantic/test/duplicate-declarations.test.ts +168 -0
  183. package/src/semantic/test/export-validation.test.ts +111 -0
  184. package/src/semantic/test/fixtures/math.ts +31 -0
  185. package/src/semantic/test/imports.test.ts +148 -0
  186. package/src/semantic/test/json-type.test.ts +68 -0
  187. package/src/semantic/test/literals.test.ts +127 -0
  188. package/src/semantic/test/model-validation.test.ts +179 -0
  189. package/src/semantic/test/prompt-validation.test.ts +343 -0
  190. package/src/semantic/test/scoping.test.ts +312 -0
  191. package/src/semantic/test/tool-validation.test.ts +306 -0
  192. package/src/semantic/test/ts-type-checking.test.ts +563 -0
  193. package/src/semantic/test/type-constraints.test.ts +111 -0
  194. package/src/semantic/test/type-inference.test.ts +87 -0
  195. package/src/semantic/test/type-validation.test.ts +552 -0
  196. package/src/semantic/test/undefined-variables.test.ts +163 -0
  197. package/src/semantic/ts-block-checker.ts +204 -0
  198. package/src/semantic/ts-signatures.ts +194 -0
  199. package/src/semantic/ts-types.ts +170 -0
  200. package/src/semantic/types.ts +58 -0
  201. package/tests/fixtures/conditional-logic.vibe +14 -0
  202. package/tests/fixtures/function-call.vibe +16 -0
  203. package/tests/fixtures/imports/cycle-detection/a.vibe +6 -0
  204. package/tests/fixtures/imports/cycle-detection/b.vibe +5 -0
  205. package/tests/fixtures/imports/cycle-detection/main.vibe +3 -0
  206. package/tests/fixtures/imports/module-isolation/main-b.vibe +8 -0
  207. package/tests/fixtures/imports/module-isolation/main.vibe +9 -0
  208. package/tests/fixtures/imports/module-isolation/moduleA.vibe +6 -0
  209. package/tests/fixtures/imports/module-isolation/moduleB.vibe +6 -0
  210. package/tests/fixtures/imports/nested-import/helper.vibe +6 -0
  211. package/tests/fixtures/imports/nested-import/main.vibe +3 -0
  212. package/tests/fixtures/imports/nested-import/utils.ts +3 -0
  213. package/tests/fixtures/imports/nested-isolation/file2.vibe +15 -0
  214. package/tests/fixtures/imports/nested-isolation/file3.vibe +10 -0
  215. package/tests/fixtures/imports/nested-isolation/main.vibe +21 -0
  216. package/tests/fixtures/imports/pure-cycle/a.vibe +5 -0
  217. package/tests/fixtures/imports/pure-cycle/b.vibe +5 -0
  218. package/tests/fixtures/imports/pure-cycle/main.vibe +3 -0
  219. package/tests/fixtures/imports/ts-boolean/checks.ts +14 -0
  220. package/tests/fixtures/imports/ts-boolean/main.vibe +10 -0
  221. package/tests/fixtures/imports/ts-boolean/type-mismatch.vibe +5 -0
  222. package/tests/fixtures/imports/ts-boolean/use-constant.vibe +18 -0
  223. package/tests/fixtures/imports/ts-error-handling/helpers.ts +42 -0
  224. package/tests/fixtures/imports/ts-error-handling/main.vibe +5 -0
  225. package/tests/fixtures/imports/ts-import/main.vibe +4 -0
  226. package/tests/fixtures/imports/ts-import/math.ts +9 -0
  227. package/tests/fixtures/imports/ts-variables/call-non-function.vibe +5 -0
  228. package/tests/fixtures/imports/ts-variables/data.ts +10 -0
  229. package/tests/fixtures/imports/ts-variables/import-json.vibe +5 -0
  230. package/tests/fixtures/imports/ts-variables/import-type-mismatch.vibe +5 -0
  231. package/tests/fixtures/imports/ts-variables/import-variable.vibe +5 -0
  232. package/tests/fixtures/imports/vibe-import/greet.vibe +5 -0
  233. package/tests/fixtures/imports/vibe-import/main.vibe +3 -0
  234. package/tests/fixtures/multiple-ai-calls.vibe +10 -0
  235. package/tests/fixtures/simple-greeting.vibe +6 -0
  236. package/tests/fixtures/template-literals.vibe +11 -0
  237. package/tests/integration/basic-ai/basic-ai.integration.test.ts +166 -0
  238. package/tests/integration/basic-ai/basic-ai.vibe +12 -0
  239. package/tests/integration/bug-fix/bug-fix.integration.test.ts +201 -0
  240. package/tests/integration/bug-fix/buggy-code.ts +22 -0
  241. package/tests/integration/bug-fix/fix-bug.vibe +21 -0
  242. package/tests/integration/compress/compress.integration.test.ts +206 -0
  243. package/tests/integration/destructuring/destructuring.integration.test.ts +92 -0
  244. package/tests/integration/hello-world-translator/hello-world-translator.integration.test.ts +61 -0
  245. package/tests/integration/line-annotator/context-modes.integration.test.ts +261 -0
  246. package/tests/integration/line-annotator/line-annotator.integration.test.ts +148 -0
  247. package/tests/integration/multi-feature/cumulative-sum.integration.test.ts +75 -0
  248. package/tests/integration/multi-feature/number-analyzer.integration.test.ts +191 -0
  249. package/tests/integration/multi-feature/number-analyzer.vibe +59 -0
  250. package/tests/integration/tool-calls/tool-calls.integration.test.ts +93 -0
@@ -0,0 +1,342 @@
1
+ import { describe, it, expect } from 'bun:test';
2
+ import { parse } from '../../parser/parse';
3
+ import { SemanticAnalyzer } from '../../semantic/analyzer';
4
+ import { createInitialState, resumeWithTsResult } from '../state';
5
+ import { runUntilPause } from '../step';
6
+ import { isVibeValue } from '../types';
7
+
8
+ const analyzer = new SemanticAnalyzer();
9
+
10
+ function getErrors(source: string): string[] {
11
+ const ast = parse(source);
12
+ return analyzer.analyze(ast, source).map((e) => e.message);
13
+ }
14
+
15
+ describe('Null Handling', () => {
16
+ describe('null literal parsing', () => {
17
+ it('parses null as a literal value', () => {
18
+ const ast = parse('let x: text = null');
19
+ expect(ast.body[0].type).toBe('LetDeclaration');
20
+ const decl = ast.body[0] as { initializer: { type: string } };
21
+ expect(decl.initializer.type).toBe('NullLiteral');
22
+ });
23
+
24
+ it('parses null in expressions', () => {
25
+ const ast = parse('let x: text = null\nlet y = x == null');
26
+ expect(ast.body.length).toBe(2);
27
+ });
28
+ });
29
+
30
+ describe('semantic validation for null assignments', () => {
31
+ it('rejects const with null initializer', () => {
32
+ const errors = getErrors('const x = null');
33
+ expect(errors.some(e => e.includes('Cannot initialize const with null'))).toBe(true);
34
+ });
35
+
36
+ it('rejects const with typed null initializer', () => {
37
+ const errors = getErrors('const x: text = null');
38
+ expect(errors.some(e => e.includes('Cannot initialize const with null'))).toBe(true);
39
+ });
40
+
41
+ it('rejects let without type annotation when initialized with null', () => {
42
+ const errors = getErrors('let x = null');
43
+ expect(errors.some(e => e.includes('Cannot infer type from null'))).toBe(true);
44
+ });
45
+
46
+ it('accepts let with type annotation initialized with null', () => {
47
+ const errors = getErrors('let x: text = null');
48
+ expect(errors.filter(e => e.includes('null'))).toEqual([]);
49
+ });
50
+
51
+ it('accepts let with json type initialized with null', () => {
52
+ const errors = getErrors('let x: json = null');
53
+ expect(errors.filter(e => e.includes('null'))).toEqual([]);
54
+ });
55
+ });
56
+
57
+ describe('null in runtime operations', () => {
58
+ it('evaluates null literal to null', () => {
59
+ const ast = parse('let x: text = null');
60
+ let state = createInitialState(ast);
61
+ state = runUntilPause(state);
62
+
63
+ expect(state.status).toBe('completed');
64
+ expect(state.callStack[0].locals['x'].value).toBe(null);
65
+ });
66
+
67
+ it('allows reassigning typed variable to null', () => {
68
+ const ast = parse(`
69
+ let x: text = "hello"
70
+ x = null
71
+ `);
72
+ let state = createInitialState(ast);
73
+ state = runUntilPause(state);
74
+
75
+ expect(state.status).toBe('completed');
76
+ expect(state.callStack[0].locals['x'].value).toBe(null);
77
+ });
78
+
79
+ it('compares null with == correctly', () => {
80
+ const ast = parse(`
81
+ let x: text = null
82
+ let isNull = x == null
83
+ let isNotNull = x != null
84
+ `);
85
+ let state = createInitialState(ast);
86
+ state = runUntilPause(state);
87
+
88
+ expect(state.status).toBe('completed');
89
+ expect(state.callStack[0].locals['isNull'].value).toBe(true);
90
+ expect(state.callStack[0].locals['isNotNull'].value).toBe(false);
91
+ });
92
+
93
+ it('string concatenation with null coerces null to empty string', () => {
94
+ const ast = parse(`
95
+ let x: text = null
96
+ let result1 = "hello " + x
97
+ let result2 = x + " world"
98
+ `);
99
+ let state = createInitialState(ast);
100
+ state = runUntilPause(state);
101
+
102
+ expect(state.status).toBe('completed');
103
+ expect(state.callStack[0].locals['result1'].value).toBe('hello ');
104
+ expect(state.callStack[0].locals['result2'].value).toBe(' world');
105
+ });
106
+
107
+ it('arithmetic with null creates error VibeValue', () => {
108
+ const ast = parse(`
109
+ let x: number = null
110
+ let result = x + 5
111
+ `);
112
+ let state = createInitialState(ast);
113
+ state = runUntilPause(state);
114
+
115
+ expect(state.status).toBe('completed');
116
+ const result = state.callStack[0].locals['result'];
117
+ expect(isVibeValue(result)).toBe(true);
118
+ expect(result.err).toBe(true); // err is now boolean
119
+ expect(result.errDetails?.message).toContain('null');
120
+ });
121
+
122
+ it('subtraction with null creates error', () => {
123
+ const ast = parse(`
124
+ let x: number = null
125
+ let result = 10 - x
126
+ `);
127
+ let state = createInitialState(ast);
128
+ state = runUntilPause(state);
129
+
130
+ expect(state.status).toBe('completed');
131
+ const result = state.callStack[0].locals['result'];
132
+ expect(result.err).toBe(true); // err is now boolean
133
+ });
134
+
135
+ it('multiplication with null creates error', () => {
136
+ const ast = parse(`
137
+ let x: number = null
138
+ let result = x * 5
139
+ `);
140
+ let state = createInitialState(ast);
141
+ state = runUntilPause(state);
142
+
143
+ expect(state.status).toBe('completed');
144
+ const result = state.callStack[0].locals['result'];
145
+ expect(result.err).toBe(true); // err is now boolean
146
+ });
147
+
148
+ it('unary minus on null creates error', () => {
149
+ const ast = parse(`
150
+ let x: number = null
151
+ let result = -x
152
+ `);
153
+ let state = createInitialState(ast);
154
+ state = runUntilPause(state);
155
+
156
+ expect(state.status).toBe('completed');
157
+ const result = state.callStack[0].locals['result'];
158
+ expect(result.err).toBe(true); // err is now boolean
159
+ expect(result.errDetails?.message).toContain('null');
160
+ });
161
+
162
+ it('logical operators treat null as falsy', () => {
163
+ const ast = parse(`
164
+ let x: text = null
165
+ let andResult = x and true
166
+ let orResult = x or "default"
167
+ `);
168
+ let state = createInitialState(ast);
169
+ state = runUntilPause(state);
170
+
171
+ expect(state.status).toBe('completed');
172
+ expect(state.callStack[0].locals['andResult'].value).toBe(false);
173
+ expect(state.callStack[0].locals['orResult'].value).toBe(true);
174
+ });
175
+ });
176
+
177
+ describe('error propagation through expressions (docs example)', () => {
178
+ it('error propagates through a + b when a has error', () => {
179
+ // Matches the docs example: let sum = a + b where a might fail
180
+ const ast = parse(`
181
+ let a: number = null
182
+ let b = 10
183
+ let sum = a + b
184
+ `);
185
+ let state = createInitialState(ast);
186
+ state = runUntilPause(state);
187
+
188
+ expect(state.status).toBe('completed');
189
+ // sum should have the error from a
190
+ const sum = state.callStack[0].locals['sum'];
191
+ expect(isVibeValue(sum)).toBe(true);
192
+ expect(sum.err).toBe(true);
193
+ expect(sum.errDetails?.message).toContain('null');
194
+ });
195
+
196
+ it('error propagates through a + b when b has error', () => {
197
+ const ast = parse(`
198
+ let a = 10
199
+ let b: number = null
200
+ let sum = a + b
201
+ `);
202
+ let state = createInitialState(ast);
203
+ state = runUntilPause(state);
204
+
205
+ expect(state.status).toBe('completed');
206
+ const sum = state.callStack[0].locals['sum'];
207
+ expect(sum.err).toBe(true);
208
+ });
209
+
210
+ it('first error wins when both operands have errors', () => {
211
+ const ast = parse(`
212
+ let a: number = null
213
+ let b: number = null
214
+ let sum = a + b
215
+ `);
216
+ let state = createInitialState(ast);
217
+ state = runUntilPause(state);
218
+
219
+ expect(state.status).toBe('completed');
220
+ const sum = state.callStack[0].locals['sum'];
221
+ expect(sum.err).toBe(true);
222
+ // Should contain error from left operand
223
+ expect(sum.errDetails?.message).toContain('null');
224
+ });
225
+
226
+ it('error propagates through chain of operations', () => {
227
+ // Docs example: errors flow through naturally
228
+ const ast = parse(`
229
+ let a: number = null
230
+ let b = 10
231
+ let sum = a + b
232
+ let doubled = sum * 2
233
+ `);
234
+ let state = createInitialState(ast);
235
+ state = runUntilPause(state);
236
+
237
+ expect(state.status).toBe('completed');
238
+ // doubled should have the propagated error
239
+ const doubled = state.callStack[0].locals['doubled'];
240
+ expect(doubled.err).toBe(true);
241
+ expect(doubled.errDetails?.message).toContain('null');
242
+ });
243
+
244
+ it('.err can be checked on final result of chain', () => {
245
+ const ast = parse(`
246
+ let a: number = null
247
+ let b = 10
248
+ let sum = a + b
249
+ let doubled = sum * 2
250
+ let hasError = doubled.err
251
+ `);
252
+ let state = createInitialState(ast);
253
+ state = runUntilPause(state);
254
+
255
+ expect(state.status).toBe('completed');
256
+ expect(state.callStack[0].locals['hasError'].value).toBe(true);
257
+ });
258
+
259
+ it('no error when all values are valid', () => {
260
+ const ast = parse(`
261
+ let a = 5
262
+ let b = 10
263
+ let sum = a + b
264
+ let doubled = sum * 2
265
+ let hasError = doubled.err
266
+ `);
267
+ let state = createInitialState(ast);
268
+ state = runUntilPause(state);
269
+
270
+ expect(state.status).toBe('completed');
271
+ expect(state.callStack[0].locals['doubled'].value).toBe(30);
272
+ expect(state.callStack[0].locals['hasError'].value).toBe(false);
273
+ });
274
+
275
+ it('error preserved when assigned to another variable', () => {
276
+ const ast = parse(`
277
+ let a: number = null
278
+ let b = a + 5
279
+ let c = b
280
+ let hasError = c.err
281
+ `);
282
+ let state = createInitialState(ast);
283
+ state = runUntilPause(state);
284
+
285
+ expect(state.status).toBe('completed');
286
+ const c = state.callStack[0].locals['c'];
287
+ expect(c.err).toBe(true);
288
+ expect(state.callStack[0].locals['hasError'].value).toBe(true);
289
+ });
290
+ });
291
+
292
+ describe('JS interop - undefined to null normalization', () => {
293
+ it('normalizes ts block undefined return to null', () => {
294
+ const ast = parse(`
295
+ let result = ts() { return undefined }
296
+ `);
297
+ let state = createInitialState(ast);
298
+ state = runUntilPause(state);
299
+
300
+ expect(state.status).toBe('awaiting_ts');
301
+
302
+ // Resume with undefined from TS
303
+ state = resumeWithTsResult(state, undefined);
304
+ state = runUntilPause(state);
305
+
306
+ expect(state.status).toBe('completed');
307
+ expect(state.callStack[0].locals['result'].value).toBe(null);
308
+ });
309
+
310
+ it('preserves actual null from ts block', () => {
311
+ const ast = parse(`
312
+ let result = ts() { return null }
313
+ `);
314
+ let state = createInitialState(ast);
315
+ state = runUntilPause(state);
316
+
317
+ expect(state.status).toBe('awaiting_ts');
318
+
319
+ state = resumeWithTsResult(state, null);
320
+ state = runUntilPause(state);
321
+
322
+ expect(state.status).toBe('completed');
323
+ expect(state.callStack[0].locals['result'].value).toBe(null);
324
+ });
325
+
326
+ it('preserves other values from ts block', () => {
327
+ const ast = parse(`
328
+ let result = ts() { return 42 }
329
+ `);
330
+ let state = createInitialState(ast);
331
+ state = runUntilPause(state);
332
+
333
+ expect(state.status).toBe('awaiting_ts');
334
+
335
+ state = resumeWithTsResult(state, 42);
336
+ state = runUntilPause(state);
337
+
338
+ expect(state.status).toBe('completed');
339
+ expect(state.callStack[0].locals['result'].value).toBe(42);
340
+ });
341
+ });
342
+ });
@@ -0,0 +1,332 @@
1
+ import { describe, it, expect } from 'bun:test';
2
+ import { parse } from '../../parser/parse';
3
+ import { createInitialState, currentFrame } from '../state';
4
+ import { step } from '../step';
5
+ import { buildLocalContext, buildGlobalContext, formatContextForAI } from '../context';
6
+
7
+ describe('private variable visibility', () => {
8
+ describe('runtime state', () => {
9
+ it('stores isPrivate flag in VibeValue', () => {
10
+ const code = 'let private secret: text = "hidden"';
11
+ const ast = parse(code);
12
+ let state = createInitialState(ast);
13
+
14
+ // Execute until complete
15
+ while (state.status === 'running') {
16
+ state = step(state);
17
+ }
18
+
19
+ const frame = currentFrame(state);
20
+ expect(frame.locals['secret']).toBeDefined();
21
+ expect(frame.locals['secret'].value).toBe('hidden');
22
+ expect(frame.locals['secret'].isPrivate).toBe(true);
23
+ });
24
+
25
+ it('stores isPrivate flag in orderedEntries', () => {
26
+ const code = 'let private secret: text = "hidden"';
27
+ const ast = parse(code);
28
+ let state = createInitialState(ast);
29
+
30
+ while (state.status === 'running') {
31
+ state = step(state);
32
+ }
33
+
34
+ const frame = currentFrame(state);
35
+ const entry = frame.orderedEntries.find(
36
+ (e) => e.kind === 'variable' && e.name === 'secret'
37
+ );
38
+ expect(entry).toBeDefined();
39
+ expect(entry?.kind).toBe('variable');
40
+ if (entry?.kind === 'variable') {
41
+ expect(entry.isPrivate).toBe(true);
42
+ }
43
+ });
44
+
45
+ it('public variables do not have isPrivate flag', () => {
46
+ const code = 'let visible: text = "shown"';
47
+ const ast = parse(code);
48
+ let state = createInitialState(ast);
49
+
50
+ while (state.status === 'running') {
51
+ state = step(state);
52
+ }
53
+
54
+ const frame = currentFrame(state);
55
+ expect(frame.locals['visible']).toBeDefined();
56
+ expect(frame.locals['visible'].isPrivate).toBeUndefined();
57
+ });
58
+ });
59
+
60
+ describe('context filtering', () => {
61
+ it('filters private variables from local context', () => {
62
+ const code = `
63
+ let private secret: text = "hidden"
64
+ let visible: text = "shown"
65
+ `;
66
+ const ast = parse(code);
67
+ let state = createInitialState(ast);
68
+
69
+ while (state.status === 'running') {
70
+ state = step(state);
71
+ }
72
+
73
+ const context = buildLocalContext(state);
74
+ const varNames = context
75
+ .filter((e) => e.kind === 'variable')
76
+ .map((e) => (e as { name: string }).name);
77
+
78
+ expect(varNames).toContain('visible');
79
+ expect(varNames).not.toContain('secret');
80
+ });
81
+
82
+ it('filters private variables from global context', () => {
83
+ const code = `
84
+ let private secret: text = "hidden"
85
+ let visible: text = "shown"
86
+ const private API_KEY: text = "key"
87
+ const PUBLIC: text = "pub"
88
+ `;
89
+ const ast = parse(code);
90
+ let state = createInitialState(ast);
91
+
92
+ while (state.status === 'running') {
93
+ state = step(state);
94
+ }
95
+
96
+ const context = buildGlobalContext(state);
97
+ const varNames = context
98
+ .filter((e) => e.kind === 'variable')
99
+ .map((e) => (e as { name: string }).name);
100
+
101
+ expect(varNames).toContain('visible');
102
+ expect(varNames).toContain('PUBLIC');
103
+ expect(varNames).not.toContain('secret');
104
+ expect(varNames).not.toContain('API_KEY');
105
+ });
106
+
107
+ it('private variables still exist in runtime but hidden from context', () => {
108
+ const code = `
109
+ let private secret: text = "hidden"
110
+ let visible: text = "shown"
111
+ `;
112
+ const ast = parse(code);
113
+ let state = createInitialState(ast);
114
+
115
+ while (state.status === 'running') {
116
+ state = step(state);
117
+ }
118
+
119
+ const frame = currentFrame(state);
120
+
121
+ // Both exist in locals
122
+ expect(frame.locals['secret']).toBeDefined();
123
+ expect(frame.locals['visible']).toBeDefined();
124
+
125
+ // Only visible appears in context
126
+ const context = buildLocalContext(state);
127
+ expect(context.length).toBe(1);
128
+ expect((context[0] as { name: string }).name).toBe('visible');
129
+ });
130
+
131
+ it('filters private fields in destructuring from context', () => {
132
+ // For destructuring, we test that the orderedEntries correctly store isPrivate
133
+ const code = 'let private x: text = "a"';
134
+ const ast = parse(code);
135
+ let state = createInitialState(ast);
136
+
137
+ while (state.status === 'running') {
138
+ state = step(state);
139
+ }
140
+
141
+ const frame = currentFrame(state);
142
+ const entry = frame.orderedEntries[0];
143
+ expect(entry.kind).toBe('variable');
144
+ if (entry.kind === 'variable') {
145
+ expect(entry.name).toBe('x');
146
+ expect(entry.isPrivate).toBe(true);
147
+ }
148
+
149
+ // Verify filtered from context
150
+ const context = buildLocalContext(state);
151
+ expect(context.length).toBe(0);
152
+ });
153
+ });
154
+
155
+ describe('mixed visibility scenarios', () => {
156
+ it('handles multiple private and public vars correctly', () => {
157
+ const code = `
158
+ let private a: text = "1"
159
+ let b: text = "2"
160
+ let private c: text = "3"
161
+ let d: text = "4"
162
+ let private e: text = "5"
163
+ `;
164
+ const ast = parse(code);
165
+ let state = createInitialState(ast);
166
+
167
+ while (state.status === 'running') {
168
+ state = step(state);
169
+ }
170
+
171
+ const context = buildLocalContext(state);
172
+ const varNames = context
173
+ .filter((e) => e.kind === 'variable')
174
+ .map((e) => (e as { name: string }).name);
175
+
176
+ // Only b and d should be visible
177
+ expect(varNames).toEqual(['b', 'd']);
178
+ });
179
+ });
180
+
181
+ describe('destructuring with mixed visibility', () => {
182
+ it('simulates destructuring: private x is hidden, public y is visible', () => {
183
+ // Destructuring requires a do/vibe expression which needs AI.
184
+ // We simulate the result by manually creating variables as if
185
+ // `let {private x: text, y: number} = do "..." model` had executed.
186
+
187
+ const code = 'let placeholder = 0'; // Need some code to initialize state
188
+ const ast = parse(code);
189
+ let state = createInitialState(ast);
190
+
191
+ // Execute to get initial state set up
192
+ while (state.status === 'running') {
193
+ state = step(state);
194
+ }
195
+
196
+ // Now manually add variables as if destructuring had happened
197
+ const frame = currentFrame(state);
198
+
199
+ // Simulate: let {private x: text, y: number} = ...
200
+ // x is private (isPrivate: true), y is public (no isPrivate)
201
+ const newLocals = {
202
+ ...frame.locals,
203
+ x: {
204
+ value: 'secret-value',
205
+ err: false,
206
+ errDetails: null,
207
+ toolCalls: [],
208
+ isConst: false,
209
+ typeAnnotation: 'text' as const,
210
+ source: 'ai' as const,
211
+ isPrivate: true, // x is private
212
+ },
213
+ y: {
214
+ value: 42,
215
+ err: false,
216
+ errDetails: null,
217
+ toolCalls: [],
218
+ isConst: false,
219
+ typeAnnotation: 'number' as const,
220
+ source: 'ai' as const,
221
+ // y has no isPrivate (public)
222
+ },
223
+ };
224
+
225
+ const newOrderedEntries = [
226
+ ...frame.orderedEntries,
227
+ {
228
+ kind: 'variable' as const,
229
+ name: 'x',
230
+ value: 'secret-value',
231
+ type: 'text',
232
+ isConst: false,
233
+ source: 'ai' as const,
234
+ isPrivate: true, // x is private
235
+ },
236
+ {
237
+ kind: 'variable' as const,
238
+ name: 'y',
239
+ value: 42,
240
+ type: 'number',
241
+ isConst: false,
242
+ source: 'ai' as const,
243
+ // y has no isPrivate (public)
244
+ },
245
+ ];
246
+
247
+ state = {
248
+ ...state,
249
+ callStack: [
250
+ ...state.callStack.slice(0, -1),
251
+ { ...frame, locals: newLocals, orderedEntries: newOrderedEntries },
252
+ ],
253
+ };
254
+
255
+ // Verify both exist in runtime
256
+ const updatedFrame = currentFrame(state);
257
+ expect(updatedFrame.locals['x']).toBeDefined();
258
+ expect(updatedFrame.locals['x'].value).toBe('secret-value');
259
+ expect(updatedFrame.locals['x'].isPrivate).toBe(true);
260
+
261
+ expect(updatedFrame.locals['y']).toBeDefined();
262
+ expect(updatedFrame.locals['y'].value).toBe(42);
263
+ expect(updatedFrame.locals['y'].isPrivate).toBeUndefined();
264
+
265
+ // Verify LOCAL context filtering: x hidden, y visible
266
+ const localContext = buildLocalContext(state);
267
+ const localVarNames = localContext
268
+ .filter((e) => e.kind === 'variable')
269
+ .map((e) => (e as { name: string }).name);
270
+
271
+ expect(localVarNames).toContain('y');
272
+ expect(localVarNames).not.toContain('x');
273
+ expect(localVarNames).toContain('placeholder'); // public
274
+
275
+ // Verify GLOBAL context filtering: x hidden, y visible
276
+ const globalContext = buildGlobalContext(state);
277
+ const globalVarNames = globalContext
278
+ .filter((e) => e.kind === 'variable')
279
+ .map((e) => (e as { name: string }).name);
280
+
281
+ expect(globalVarNames).toContain('y');
282
+ expect(globalVarNames).not.toContain('x');
283
+ expect(globalVarNames).toContain('placeholder'); // public
284
+
285
+ // Verify FORMATTED TEXT doesn't contain private variable
286
+ // This is the actual text sent to AI
287
+ const formattedLocal = formatContextForAI(localContext);
288
+ expect(formattedLocal.text).toContain('y');
289
+ expect(formattedLocal.text).not.toContain('secret-value'); // x's value
290
+ expect(formattedLocal.text).not.toMatch(/\bx\b.*secret/); // x: secret pattern
291
+
292
+ const formattedGlobal = formatContextForAI(globalContext);
293
+ expect(formattedGlobal.text).toContain('y');
294
+ expect(formattedGlobal.text).not.toContain('secret-value');
295
+ expect(formattedGlobal.text).not.toMatch(/\bx\b.*secret/);
296
+ });
297
+ });
298
+
299
+ describe('formatted context text', () => {
300
+ it('private variables do not appear in AI context text', () => {
301
+ const code = `
302
+ let private secret: text = "super-secret-password"
303
+ let visible: text = "public-data"
304
+ `;
305
+ const ast = parse(code);
306
+ let state = createInitialState(ast);
307
+
308
+ while (state.status === 'running') {
309
+ state = step(state);
310
+ }
311
+
312
+ const localContext = buildLocalContext(state);
313
+ const globalContext = buildGlobalContext(state);
314
+
315
+ // Format context as it would be sent to AI
316
+ const formattedLocal = formatContextForAI(localContext);
317
+ const formattedGlobal = formatContextForAI(globalContext);
318
+
319
+ // Visible variable should appear in text
320
+ expect(formattedLocal.text).toContain('visible');
321
+ expect(formattedLocal.text).toContain('public-data');
322
+ expect(formattedGlobal.text).toContain('visible');
323
+ expect(formattedGlobal.text).toContain('public-data');
324
+
325
+ // Private variable should NOT appear in text
326
+ expect(formattedLocal.text).not.toContain('secret');
327
+ expect(formattedLocal.text).not.toContain('super-secret-password');
328
+ expect(formattedGlobal.text).not.toContain('secret');
329
+ expect(formattedGlobal.text).not.toContain('super-secret-password');
330
+ });
331
+ });
332
+ });