@vibe-lang/runtime 0.2.8 → 0.2.9

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-lang/runtime",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "description": "Vibe language runtime and CLI",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -316,6 +316,10 @@ class VibeParser extends CstParser {
316
316
  this.CONSUME(T.Comma);
317
317
  this.SUBRULE2(this.property);
318
318
  });
319
+ // Allow trailing comma
320
+ this.OPTION(() => {
321
+ this.CONSUME2(T.Comma);
322
+ });
319
323
  });
320
324
 
321
325
  private property = this.RULE('property', () => {
@@ -782,6 +786,10 @@ class VibeParser extends CstParser {
782
786
  this.CONSUME(T.Comma);
783
787
  this.SUBRULE2(this.expression);
784
788
  });
789
+ // Allow trailing comma
790
+ this.OPTION(() => {
791
+ this.CONSUME2(T.Comma);
792
+ });
785
793
  });
786
794
  }
787
795
 
@@ -17,6 +17,7 @@ export interface ParseOptions {
17
17
  function improveErrorMessage(error: IRecognitionException): string {
18
18
  const ruleStack = error.context?.ruleStack ?? [];
19
19
  const previousToken = error.previousToken;
20
+ const currentToken = error.token;
20
21
  const message = error.message;
21
22
 
22
23
  // Missing type annotation for function/tool parameter
@@ -29,6 +30,26 @@ function improveErrorMessage(error: IRecognitionException): string {
29
30
  return `Missing type annotation for parameter '${previousToken.image}'`;
30
31
  }
31
32
 
33
+ // Missing comma between properties in object/model declaration
34
+ // Detected: in objectLiteral/objectLiteralExpr, expected RBrace, found Identifier
35
+ if (
36
+ (ruleStack.includes('objectLiteral') || ruleStack.includes('objectLiteralExpr')) &&
37
+ message.includes('RBrace') &&
38
+ currentToken?.tokenType?.name === 'Identifier'
39
+ ) {
40
+ return `Missing comma between properties. Add ',' after the previous property`;
41
+ }
42
+
43
+ // Missing comma between elements in array
44
+ // Detected: in arrayLiteral, expected RBracket, found something else
45
+ if (
46
+ ruleStack.includes('arrayLiteral') &&
47
+ message.includes('RBracket') &&
48
+ currentToken?.tokenType?.name !== 'RBracket'
49
+ ) {
50
+ return `Missing comma between array elements. Add ',' after the previous element`;
51
+ }
52
+
32
53
  return message;
33
54
  }
34
55
 
@@ -157,4 +157,29 @@ foo(
157
157
  greet("hello"
158
158
  `)).toThrow();
159
159
  });
160
+
161
+ // ============================================================================
162
+ // missing comma errors
163
+ // ============================================================================
164
+
165
+ test('object literal missing comma between properties', () => {
166
+ expect(() => parse(`let x = { a: 1 b: 2 }`)).toThrow(
167
+ "Missing comma between properties"
168
+ );
169
+ });
170
+
171
+ test('model declaration missing comma between properties', () => {
172
+ expect(() => parse(`
173
+ model m = {
174
+ name: "test"
175
+ apiKey: "key"
176
+ }
177
+ `)).toThrow("Missing comma between properties");
178
+ });
179
+
180
+ test('array literal missing comma between elements', () => {
181
+ expect(() => parse(`let arr = [1 2 3]`)).toThrow(
182
+ "Missing comma between array elements"
183
+ );
184
+ });
160
185
  });
@@ -145,13 +145,8 @@ model myModel = {
145
145
  `)).toThrow();
146
146
  });
147
147
 
148
- test('trailing comma without next property', () => {
149
- expect(() => parse(`
150
- model myModel = {
151
- name: "test",
152
- }
153
- `)).toThrow();
154
- });
148
+ // Note: trailing comma IS now allowed (test removed)
149
+ // model myModel = { name: "test", } is valid
155
150
 
156
151
  // ============================================================================
157
152
  // Model keyword misuse
@@ -401,4 +401,33 @@ describe('Parser - For-In Statement', () => {
401
401
  const inner = outer.body.body[0];
402
402
  expect(inner.type).toBe('ForInStatement');
403
403
  });
404
+
405
+ // Trailing commas
406
+ test('object literal with trailing comma', () => {
407
+ const ast = parse('let x = { a: 1, b: 2, }');
408
+ const obj = (ast.body[0] as any).initializer;
409
+ expect(obj.type).toBe('ObjectLiteral');
410
+ expect(obj.properties).toHaveLength(2);
411
+ });
412
+
413
+ test('array literal with trailing comma', () => {
414
+ const ast = parse('let x = [1, 2, 3,]');
415
+ const arr = (ast.body[0] as any).initializer;
416
+ expect(arr.type).toBe('ArrayLiteral');
417
+ expect(arr.elements).toHaveLength(3);
418
+ });
419
+
420
+ test('single element object with trailing comma', () => {
421
+ const ast = parse('let x = { a: 1, }');
422
+ const obj = (ast.body[0] as any).initializer;
423
+ expect(obj.type).toBe('ObjectLiteral');
424
+ expect(obj.properties).toHaveLength(1);
425
+ });
426
+
427
+ test('single element array with trailing comma', () => {
428
+ const ast = parse('let x = [1,]');
429
+ const arr = (ast.body[0] as any).initializer;
430
+ expect(arr.type).toBe('ArrayLiteral');
431
+ expect(arr.elements).toHaveLength(1);
432
+ });
404
433
  });
@@ -110,6 +110,20 @@ model myModel = {
110
110
  expect(model.config.apiKey.value).toBe('sk-test');
111
111
  expect(model.config.url.value).toBe('https://api.openai.com');
112
112
  });
113
+
114
+ test('model with trailing comma', () => {
115
+ const ast = parse(`
116
+ model myModel = {
117
+ name: "gpt-4",
118
+ apiKey: "sk-test",
119
+ url: "https://api.openai.com",
120
+ }
121
+ `);
122
+ expect(ast.body).toHaveLength(1);
123
+ const model = ast.body[0] as any;
124
+ expect(model.type).toBe('ModelDeclaration');
125
+ expect(model.config.providedFields).toEqual(['name', 'apiKey', 'url']);
126
+ });
113
127
  });
114
128
 
115
129
  describe('Syntax Errors - Model Declaration', () => {
@@ -83,8 +83,15 @@ export function getReferencedVariables(expr: AST.Expression): string[] {
83
83
  }
84
84
  break;
85
85
 
86
- // Literals don't reference variables
87
86
  case 'StringLiteral':
87
+ // Extract variables from {varName} and !{varName} interpolation in strings
88
+ const stringMatches = node.value.matchAll(/!?\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g);
89
+ for (const match of stringMatches) {
90
+ variables.push(match[1]);
91
+ }
92
+ break;
93
+
94
+ // These literals don't reference variables
88
95
  case 'NumberLiteral':
89
96
  case 'BooleanLiteral':
90
97
  case 'NullLiteral':
@@ -22,11 +22,37 @@ describe('Async Dependency Detection', () => {
22
22
  expect(getReferencedVariables(expr)).toEqual(['myVar']);
23
23
  });
24
24
 
25
- test('extracts no variables from string literal', () => {
25
+ test('extracts no variables from plain string literal', () => {
26
26
  const expr = parseExpr('"hello"');
27
27
  expect(getReferencedVariables(expr)).toEqual([]);
28
28
  });
29
29
 
30
+ test('extracts variables from string literal with {var} interpolation', () => {
31
+ const expr = parseExpr('"Hello {name}!"');
32
+ const vars = getReferencedVariables(expr);
33
+ expect(vars).toContain('name');
34
+ });
35
+
36
+ test('extracts variables from string literal with !{var} expansion', () => {
37
+ const expr = parseExpr('"Process this: !{data}"');
38
+ const vars = getReferencedVariables(expr);
39
+ expect(vars).toContain('data');
40
+ });
41
+
42
+ test('extracts multiple variables from string literal interpolation', () => {
43
+ const expr = parseExpr('"Hello {greeting} {name}, your data is !{info}"');
44
+ const vars = getReferencedVariables(expr);
45
+ expect(vars).toContain('greeting');
46
+ expect(vars).toContain('name');
47
+ expect(vars).toContain('info');
48
+ });
49
+
50
+ test('extracts variables from single-quoted string with interpolation', () => {
51
+ const expr = parseExpr("'User: {user}'");
52
+ const vars = getReferencedVariables(expr);
53
+ expect(vars).toContain('user');
54
+ });
55
+
30
56
  test('extracts no variables from number literal', () => {
31
57
  const expr = parseExpr('42');
32
58
  expect(getReferencedVariables(expr)).toEqual([]);
@@ -567,12 +567,12 @@ export const runCode: VibeToolValue = {
567
567
  schema: {
568
568
  name: 'runCode',
569
569
  description:
570
- 'Execute TypeScript/JavaScript code in a sandboxed subprocess. ' +
570
+ 'Execute TypeScript code in a sandboxed subprocess. IMPORTANT: Only write TypeScript code, never Python or other languages. ' +
571
571
  'All scope variables are automatically available as local variables. ' +
572
572
  'Use `return value` to pass results back. Bun APIs are available. ' +
573
573
  'Each execution creates a unique folder in .vibe-cache/ for intermediate files.',
574
574
  parameters: [
575
- { name: 'code', type: { type: 'string' }, description: 'TypeScript/JavaScript code to execute', required: true },
575
+ { name: 'code', type: { type: 'string' }, description: 'TypeScript code to execute (not Python or other languages)', required: true },
576
576
  { name: 'scope', type: { type: 'object', additionalProperties: true }, description: 'Variables to make available in the code', required: false },
577
577
  { name: 'timeout', type: { type: 'number' }, description: 'Timeout in milliseconds (default: 30000)', required: false },
578
578
  ],