@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 +1 -1
- package/src/parser/index.ts +8 -0
- package/src/parser/parse.ts +21 -0
- package/src/parser/test/errors/missing-tokens.test.ts +25 -0
- package/src/parser/test/errors/model-declaration.test.ts +2 -7
- package/src/parser/test/literals.test.ts +29 -0
- package/src/parser/test/model-declaration.test.ts +14 -0
- package/src/runtime/async/dependencies.ts +8 -1
- package/src/runtime/async/test/dependencies.test.ts +27 -1
- package/src/runtime/stdlib/tools/index.ts +2 -2
package/package.json
CHANGED
package/src/parser/index.ts
CHANGED
|
@@ -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
|
|
package/src/parser/parse.ts
CHANGED
|
@@ -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
|
-
|
|
149
|
-
|
|
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
|
|
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
|
|
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
|
],
|