@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.
- package/package.json +46 -0
- package/src/ast/index.ts +375 -0
- package/src/ast.ts +2 -0
- package/src/debug/advanced-features.ts +482 -0
- package/src/debug/bun-inspector.ts +424 -0
- package/src/debug/handoff-manager.ts +283 -0
- package/src/debug/index.ts +150 -0
- package/src/debug/runner.ts +365 -0
- package/src/debug/server.ts +565 -0
- package/src/debug/stack-merger.ts +267 -0
- package/src/debug/state.ts +581 -0
- package/src/debug/test/advanced-features.test.ts +300 -0
- package/src/debug/test/e2e.test.ts +218 -0
- package/src/debug/test/handoff-manager.test.ts +256 -0
- package/src/debug/test/runner.test.ts +256 -0
- package/src/debug/test/stack-merger.test.ts +163 -0
- package/src/debug/test/state.test.ts +400 -0
- package/src/debug/test/ts-debug-integration.test.ts +374 -0
- package/src/debug/test/ts-import-tracker.test.ts +125 -0
- package/src/debug/test/ts-source-map.test.ts +169 -0
- package/src/debug/ts-import-tracker.ts +151 -0
- package/src/debug/ts-source-map.ts +171 -0
- package/src/errors/index.ts +124 -0
- package/src/index.ts +358 -0
- package/src/lexer/index.ts +348 -0
- package/src/lexer.ts +2 -0
- package/src/parser/index.ts +792 -0
- package/src/parser/parse.ts +45 -0
- package/src/parser/test/async.test.ts +248 -0
- package/src/parser/test/destructuring.test.ts +167 -0
- package/src/parser/test/do-expression.test.ts +486 -0
- package/src/parser/test/errors/do-expression.test.ts +95 -0
- package/src/parser/test/errors/error-locations.test.ts +230 -0
- package/src/parser/test/errors/invalid-expressions.test.ts +144 -0
- package/src/parser/test/errors/missing-tokens.test.ts +126 -0
- package/src/parser/test/errors/model-declaration.test.ts +185 -0
- package/src/parser/test/errors/nested-blocks.test.ts +226 -0
- package/src/parser/test/errors/unclosed-delimiters.test.ts +122 -0
- package/src/parser/test/errors/unexpected-tokens.test.ts +120 -0
- package/src/parser/test/import-export.test.ts +143 -0
- package/src/parser/test/literals.test.ts +404 -0
- package/src/parser/test/model-declaration.test.ts +161 -0
- package/src/parser/test/nested-blocks.test.ts +402 -0
- package/src/parser/test/parser.test.ts +743 -0
- package/src/parser/test/private.test.ts +136 -0
- package/src/parser/test/template-literal.test.ts +127 -0
- package/src/parser/test/tool-declaration.test.ts +302 -0
- package/src/parser/test/ts-block.test.ts +252 -0
- package/src/parser/test/type-annotations.test.ts +254 -0
- package/src/parser/visitor/helpers.ts +330 -0
- package/src/parser/visitor.ts +794 -0
- package/src/parser.ts +2 -0
- package/src/runtime/ai/cache-chunking.test.ts +69 -0
- package/src/runtime/ai/cache-chunking.ts +73 -0
- package/src/runtime/ai/client.ts +109 -0
- package/src/runtime/ai/context.ts +168 -0
- package/src/runtime/ai/formatters.ts +316 -0
- package/src/runtime/ai/index.ts +38 -0
- package/src/runtime/ai/language-ref.ts +38 -0
- package/src/runtime/ai/providers/anthropic.ts +253 -0
- package/src/runtime/ai/providers/google.ts +201 -0
- package/src/runtime/ai/providers/openai.ts +156 -0
- package/src/runtime/ai/retry.ts +100 -0
- package/src/runtime/ai/return-tools.ts +301 -0
- package/src/runtime/ai/test/client.test.ts +83 -0
- package/src/runtime/ai/test/formatters.test.ts +485 -0
- package/src/runtime/ai/test/retry.test.ts +137 -0
- package/src/runtime/ai/test/return-tools.test.ts +450 -0
- package/src/runtime/ai/test/tool-loop.test.ts +319 -0
- package/src/runtime/ai/test/tool-schema.test.ts +241 -0
- package/src/runtime/ai/tool-loop.ts +203 -0
- package/src/runtime/ai/tool-schema.ts +151 -0
- package/src/runtime/ai/types.ts +113 -0
- package/src/runtime/ai-logger.ts +255 -0
- package/src/runtime/ai-provider.ts +347 -0
- package/src/runtime/async/dependencies.ts +276 -0
- package/src/runtime/async/executor.ts +293 -0
- package/src/runtime/async/index.ts +43 -0
- package/src/runtime/async/scheduling.ts +163 -0
- package/src/runtime/async/test/dependencies.test.ts +284 -0
- package/src/runtime/async/test/executor.test.ts +388 -0
- package/src/runtime/context.ts +357 -0
- package/src/runtime/exec/ai.ts +139 -0
- package/src/runtime/exec/expressions.ts +475 -0
- package/src/runtime/exec/frames.ts +26 -0
- package/src/runtime/exec/functions.ts +305 -0
- package/src/runtime/exec/interpolation.ts +312 -0
- package/src/runtime/exec/statements.ts +604 -0
- package/src/runtime/exec/tools.ts +129 -0
- package/src/runtime/exec/typescript.ts +215 -0
- package/src/runtime/exec/variables.ts +279 -0
- package/src/runtime/index.ts +975 -0
- package/src/runtime/modules.ts +452 -0
- package/src/runtime/serialize.ts +103 -0
- package/src/runtime/state.ts +489 -0
- package/src/runtime/stdlib/core.ts +45 -0
- package/src/runtime/stdlib/directory.test.ts +156 -0
- package/src/runtime/stdlib/edit.test.ts +154 -0
- package/src/runtime/stdlib/fastEdit.test.ts +201 -0
- package/src/runtime/stdlib/glob.test.ts +106 -0
- package/src/runtime/stdlib/grep.test.ts +144 -0
- package/src/runtime/stdlib/index.ts +16 -0
- package/src/runtime/stdlib/readFile.test.ts +123 -0
- package/src/runtime/stdlib/tools/index.ts +707 -0
- package/src/runtime/stdlib/writeFile.test.ts +157 -0
- package/src/runtime/step.ts +969 -0
- package/src/runtime/test/ai-context.test.ts +1086 -0
- package/src/runtime/test/ai-result-object.test.ts +419 -0
- package/src/runtime/test/ai-tool-flow.test.ts +859 -0
- package/src/runtime/test/async-execution-order.test.ts +618 -0
- package/src/runtime/test/async-execution.test.ts +344 -0
- package/src/runtime/test/async-nested.test.ts +660 -0
- package/src/runtime/test/async-parallel-timing.test.ts +546 -0
- package/src/runtime/test/basic1.test.ts +154 -0
- package/src/runtime/test/binary-operators.test.ts +431 -0
- package/src/runtime/test/break-statement.test.ts +257 -0
- package/src/runtime/test/context-modes.test.ts +650 -0
- package/src/runtime/test/context.test.ts +466 -0
- package/src/runtime/test/core-functions.test.ts +228 -0
- package/src/runtime/test/e2e.test.ts +88 -0
- package/src/runtime/test/error-locations/error-locations.test.ts +80 -0
- package/src/runtime/test/error-locations/main-error.vibe +4 -0
- package/src/runtime/test/error-locations/main-import-error.vibe +3 -0
- package/src/runtime/test/error-locations/utils/helper.vibe +5 -0
- package/src/runtime/test/for-in.test.ts +312 -0
- package/src/runtime/test/helpers.ts +69 -0
- package/src/runtime/test/imports.test.ts +334 -0
- package/src/runtime/test/json-expressions.test.ts +232 -0
- package/src/runtime/test/literals.test.ts +372 -0
- package/src/runtime/test/logical-indexing.test.ts +478 -0
- package/src/runtime/test/member-methods.test.ts +324 -0
- package/src/runtime/test/model-config.test.ts +338 -0
- package/src/runtime/test/null-handling.test.ts +342 -0
- package/src/runtime/test/private-visibility.test.ts +332 -0
- package/src/runtime/test/runtime-state.test.ts +514 -0
- package/src/runtime/test/scoping.test.ts +370 -0
- package/src/runtime/test/string-interpolation.test.ts +354 -0
- package/src/runtime/test/template-literal.test.ts +181 -0
- package/src/runtime/test/tool-execution.test.ts +467 -0
- package/src/runtime/test/tool-schema-generation.test.ts +477 -0
- package/src/runtime/test/tostring.test.ts +210 -0
- package/src/runtime/test/ts-block.test.ts +594 -0
- package/src/runtime/test/ts-error-location.test.ts +231 -0
- package/src/runtime/test/types.test.ts +732 -0
- package/src/runtime/test/verbose-logger.test.ts +710 -0
- package/src/runtime/test/vibe-expression.test.ts +54 -0
- package/src/runtime/test/vibe-value-errors.test.ts +541 -0
- package/src/runtime/test/while.test.ts +232 -0
- package/src/runtime/tools/builtin.ts +30 -0
- package/src/runtime/tools/directory-tools.ts +70 -0
- package/src/runtime/tools/file-tools.ts +228 -0
- package/src/runtime/tools/index.ts +5 -0
- package/src/runtime/tools/registry.ts +48 -0
- package/src/runtime/tools/search-tools.ts +134 -0
- package/src/runtime/tools/security.ts +36 -0
- package/src/runtime/tools/system-tools.ts +312 -0
- package/src/runtime/tools/test/fixtures/base-types.ts +40 -0
- package/src/runtime/tools/test/fixtures/test-types.ts +132 -0
- package/src/runtime/tools/test/registry.test.ts +713 -0
- package/src/runtime/tools/test/security.test.ts +86 -0
- package/src/runtime/tools/test/system-tools.test.ts +679 -0
- package/src/runtime/tools/test/ts-schema.test.ts +357 -0
- package/src/runtime/tools/ts-schema.ts +341 -0
- package/src/runtime/tools/types.ts +89 -0
- package/src/runtime/tools/utility-tools.ts +198 -0
- package/src/runtime/ts-eval.ts +126 -0
- package/src/runtime/types.ts +797 -0
- package/src/runtime/validation.ts +160 -0
- package/src/runtime/verbose-logger.ts +459 -0
- package/src/runtime.ts +2 -0
- package/src/semantic/analyzer-context.ts +62 -0
- package/src/semantic/analyzer-validators.ts +575 -0
- package/src/semantic/analyzer-visitors.ts +534 -0
- package/src/semantic/analyzer.ts +83 -0
- package/src/semantic/index.ts +11 -0
- package/src/semantic/symbol-table.ts +58 -0
- package/src/semantic/test/async-validation.test.ts +301 -0
- package/src/semantic/test/compress-validation.test.ts +179 -0
- package/src/semantic/test/const-reassignment.test.ts +111 -0
- package/src/semantic/test/control-flow.test.ts +346 -0
- package/src/semantic/test/destructuring.test.ts +185 -0
- package/src/semantic/test/duplicate-declarations.test.ts +168 -0
- package/src/semantic/test/export-validation.test.ts +111 -0
- package/src/semantic/test/fixtures/math.ts +31 -0
- package/src/semantic/test/imports.test.ts +148 -0
- package/src/semantic/test/json-type.test.ts +68 -0
- package/src/semantic/test/literals.test.ts +127 -0
- package/src/semantic/test/model-validation.test.ts +179 -0
- package/src/semantic/test/prompt-validation.test.ts +343 -0
- package/src/semantic/test/scoping.test.ts +312 -0
- package/src/semantic/test/tool-validation.test.ts +306 -0
- package/src/semantic/test/ts-type-checking.test.ts +563 -0
- package/src/semantic/test/type-constraints.test.ts +111 -0
- package/src/semantic/test/type-inference.test.ts +87 -0
- package/src/semantic/test/type-validation.test.ts +552 -0
- package/src/semantic/test/undefined-variables.test.ts +163 -0
- package/src/semantic/ts-block-checker.ts +204 -0
- package/src/semantic/ts-signatures.ts +194 -0
- package/src/semantic/ts-types.ts +170 -0
- package/src/semantic/types.ts +58 -0
- package/tests/fixtures/conditional-logic.vibe +14 -0
- package/tests/fixtures/function-call.vibe +16 -0
- package/tests/fixtures/imports/cycle-detection/a.vibe +6 -0
- package/tests/fixtures/imports/cycle-detection/b.vibe +5 -0
- package/tests/fixtures/imports/cycle-detection/main.vibe +3 -0
- package/tests/fixtures/imports/module-isolation/main-b.vibe +8 -0
- package/tests/fixtures/imports/module-isolation/main.vibe +9 -0
- package/tests/fixtures/imports/module-isolation/moduleA.vibe +6 -0
- package/tests/fixtures/imports/module-isolation/moduleB.vibe +6 -0
- package/tests/fixtures/imports/nested-import/helper.vibe +6 -0
- package/tests/fixtures/imports/nested-import/main.vibe +3 -0
- package/tests/fixtures/imports/nested-import/utils.ts +3 -0
- package/tests/fixtures/imports/nested-isolation/file2.vibe +15 -0
- package/tests/fixtures/imports/nested-isolation/file3.vibe +10 -0
- package/tests/fixtures/imports/nested-isolation/main.vibe +21 -0
- package/tests/fixtures/imports/pure-cycle/a.vibe +5 -0
- package/tests/fixtures/imports/pure-cycle/b.vibe +5 -0
- package/tests/fixtures/imports/pure-cycle/main.vibe +3 -0
- package/tests/fixtures/imports/ts-boolean/checks.ts +14 -0
- package/tests/fixtures/imports/ts-boolean/main.vibe +10 -0
- package/tests/fixtures/imports/ts-boolean/type-mismatch.vibe +5 -0
- package/tests/fixtures/imports/ts-boolean/use-constant.vibe +18 -0
- package/tests/fixtures/imports/ts-error-handling/helpers.ts +42 -0
- package/tests/fixtures/imports/ts-error-handling/main.vibe +5 -0
- package/tests/fixtures/imports/ts-import/main.vibe +4 -0
- package/tests/fixtures/imports/ts-import/math.ts +9 -0
- package/tests/fixtures/imports/ts-variables/call-non-function.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/data.ts +10 -0
- package/tests/fixtures/imports/ts-variables/import-json.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/import-type-mismatch.vibe +5 -0
- package/tests/fixtures/imports/ts-variables/import-variable.vibe +5 -0
- package/tests/fixtures/imports/vibe-import/greet.vibe +5 -0
- package/tests/fixtures/imports/vibe-import/main.vibe +3 -0
- package/tests/fixtures/multiple-ai-calls.vibe +10 -0
- package/tests/fixtures/simple-greeting.vibe +6 -0
- package/tests/fixtures/template-literals.vibe +11 -0
- package/tests/integration/basic-ai/basic-ai.integration.test.ts +166 -0
- package/tests/integration/basic-ai/basic-ai.vibe +12 -0
- package/tests/integration/bug-fix/bug-fix.integration.test.ts +201 -0
- package/tests/integration/bug-fix/buggy-code.ts +22 -0
- package/tests/integration/bug-fix/fix-bug.vibe +21 -0
- package/tests/integration/compress/compress.integration.test.ts +206 -0
- package/tests/integration/destructuring/destructuring.integration.test.ts +92 -0
- package/tests/integration/hello-world-translator/hello-world-translator.integration.test.ts +61 -0
- package/tests/integration/line-annotator/context-modes.integration.test.ts +261 -0
- package/tests/integration/line-annotator/line-annotator.integration.test.ts +148 -0
- package/tests/integration/multi-feature/cumulative-sum.integration.test.ts +75 -0
- package/tests/integration/multi-feature/number-analyzer.integration.test.ts +191 -0
- package/tests/integration/multi-feature/number-analyzer.vibe +59 -0
- package/tests/integration/tool-calls/tool-calls.integration.test.ts +93 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic Analyzer Validators
|
|
3
|
+
*
|
|
4
|
+
* Validation functions for the semantic analyzer.
|
|
5
|
+
* Each function takes an AnalyzerContext and performs specific validation.
|
|
6
|
+
*/
|
|
7
|
+
import * as AST from '../ast';
|
|
8
|
+
import type { SourceLocation } from '../errors';
|
|
9
|
+
import type { AnalyzerContext } from './analyzer-context';
|
|
10
|
+
import { isValidType, typesCompatible, isValidJson, getBaseType } from './types';
|
|
11
|
+
import { ESCAPED_LBRACE, ESCAPED_RBRACE, ESCAPED_BANG_LBRACE } from '../parser/visitor/helpers';
|
|
12
|
+
import { isCoreFunction } from '../runtime/stdlib/core';
|
|
13
|
+
import { checkTsBlockTypes, inferTsBlockReturnType } from './ts-block-checker';
|
|
14
|
+
import { tsTypeToVibe, isVibeTypeCompatibleWithTs } from './ts-types';
|
|
15
|
+
import type { TsFunctionSignature } from './ts-signatures';
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Model and Tool Validation
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Validates model declaration configuration.
|
|
23
|
+
*/
|
|
24
|
+
export function validateModelConfig(
|
|
25
|
+
ctx: AnalyzerContext,
|
|
26
|
+
node: AST.ModelDeclaration,
|
|
27
|
+
visitExpression: (node: AST.Expression) => void
|
|
28
|
+
): void {
|
|
29
|
+
const config = node.config;
|
|
30
|
+
const requiredFields = ['name', 'apiKey', 'url'];
|
|
31
|
+
const optionalFields = ['provider', 'maxRetriesOnError', 'thinkingLevel', 'tools'];
|
|
32
|
+
const validFields = [...requiredFields, ...optionalFields];
|
|
33
|
+
const provided = new Set(config.providedFields);
|
|
34
|
+
|
|
35
|
+
// Check for missing required fields
|
|
36
|
+
for (const field of requiredFields) {
|
|
37
|
+
if (!provided.has(field)) {
|
|
38
|
+
ctx.error(`Model '${node.name}' is missing required field '${field}'`, node.location);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Check for unknown fields
|
|
43
|
+
for (const field of config.providedFields) {
|
|
44
|
+
if (!validFields.includes(field)) {
|
|
45
|
+
ctx.error(`Model '${node.name}' has unknown field '${field}'`, node.location);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Validate provider is one of the allowed values
|
|
50
|
+
if (config.provider) {
|
|
51
|
+
if (config.provider.type === 'StringLiteral') {
|
|
52
|
+
const validProviders = ['anthropic', 'openai', 'google'];
|
|
53
|
+
if (!validProviders.includes(config.provider.value)) {
|
|
54
|
+
ctx.error(
|
|
55
|
+
`Invalid provider '${config.provider.value}'. Must be: ${validProviders.join(', ')}`,
|
|
56
|
+
config.provider.location
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
visitExpression(config.provider);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Validate maxRetriesOnError is a non-negative number
|
|
64
|
+
if (config.maxRetriesOnError) {
|
|
65
|
+
if (config.maxRetriesOnError.type === 'NumberLiteral') {
|
|
66
|
+
if (config.maxRetriesOnError.value < 0 || !Number.isInteger(config.maxRetriesOnError.value)) {
|
|
67
|
+
ctx.error(
|
|
68
|
+
`maxRetriesOnError must be a non-negative integer, got ${config.maxRetriesOnError.value}`,
|
|
69
|
+
config.maxRetriesOnError.location
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
visitExpression(config.maxRetriesOnError);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Visit field expressions (check for undefined variables, etc.)
|
|
77
|
+
if (config.modelName) visitExpression(config.modelName);
|
|
78
|
+
if (config.apiKey) visitExpression(config.apiKey);
|
|
79
|
+
if (config.url) visitExpression(config.url);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Validates tool declaration.
|
|
84
|
+
*/
|
|
85
|
+
export function validateToolDeclaration(ctx: AnalyzerContext, node: AST.ToolDeclaration): void {
|
|
86
|
+
// Validate @param decorators reference actual parameters
|
|
87
|
+
if (node.paramDecorators) {
|
|
88
|
+
const paramNames = new Set(node.params.map(p => p.name));
|
|
89
|
+
for (const decoratorName of node.paramDecorators) {
|
|
90
|
+
if (!paramNames.has(decoratorName)) {
|
|
91
|
+
ctx.error(
|
|
92
|
+
`@param '${decoratorName}' does not match any parameter in tool '${node.name}'. ` +
|
|
93
|
+
`Valid parameters: ${node.params.map(p => p.name).join(', ') || '(none)'}`,
|
|
94
|
+
node.location
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Validate parameter type annotations (allow both Vibe types and imported types)
|
|
101
|
+
for (const param of node.params) {
|
|
102
|
+
const baseType = param.typeAnnotation.replace(/\[\]$/, '');
|
|
103
|
+
const isVibeType = ['text', 'json', 'boolean', 'number', 'prompt'].includes(baseType);
|
|
104
|
+
if (!isVibeType) {
|
|
105
|
+
const symbol = ctx.symbols.lookup(baseType);
|
|
106
|
+
if (!symbol) {
|
|
107
|
+
ctx.error(
|
|
108
|
+
`Unknown type '${baseType}' in tool parameter '${param.name}'`,
|
|
109
|
+
node.location
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Validate return type if present
|
|
116
|
+
if (node.returnType) {
|
|
117
|
+
const baseType = node.returnType.replace(/\[\]$/, '');
|
|
118
|
+
const isVibeType = ['text', 'json', 'boolean', 'number', 'prompt'].includes(baseType);
|
|
119
|
+
if (!isVibeType) {
|
|
120
|
+
const symbol = ctx.symbols.lookup(baseType);
|
|
121
|
+
if (!symbol) {
|
|
122
|
+
ctx.error(
|
|
123
|
+
`Unknown return type '${baseType}' in tool '${node.name}'`,
|
|
124
|
+
node.location
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ============================================================================
|
|
132
|
+
// Type Validation
|
|
133
|
+
// ============================================================================
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Validates a type annotation is valid.
|
|
137
|
+
*/
|
|
138
|
+
export function validateTypeAnnotation(ctx: AnalyzerContext, type: string, location: SourceLocation): void {
|
|
139
|
+
if (!isValidType(type)) {
|
|
140
|
+
ctx.error(`Unknown type '${getBaseType(type)}'`, location);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Validates a JSON literal string.
|
|
146
|
+
*/
|
|
147
|
+
export function validateJsonLiteral(ctx: AnalyzerContext, value: string, location: SourceLocation): void {
|
|
148
|
+
if (!isValidJson(value)) {
|
|
149
|
+
ctx.error(`Invalid JSON literal`, location);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Validates that a condition expression is boolean.
|
|
155
|
+
*/
|
|
156
|
+
export function validateConditionType(
|
|
157
|
+
ctx: AnalyzerContext,
|
|
158
|
+
expr: AST.Expression,
|
|
159
|
+
conditionContext: 'if' | 'while',
|
|
160
|
+
getExpressionType: (expr: AST.Expression) => string | null
|
|
161
|
+
): void {
|
|
162
|
+
const exprType = getExpressionType(expr);
|
|
163
|
+
if (exprType && exprType !== 'boolean') {
|
|
164
|
+
ctx.error(`${conditionContext} condition must be boolean, got ${exprType}`, expr.location);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Validates that an expression is compatible with its type annotation.
|
|
170
|
+
*/
|
|
171
|
+
export function validateLiteralType(
|
|
172
|
+
ctx: AnalyzerContext,
|
|
173
|
+
expr: AST.Expression,
|
|
174
|
+
type: string,
|
|
175
|
+
location: SourceLocation,
|
|
176
|
+
getExpressionType: (expr: AST.Expression) => string | null
|
|
177
|
+
): void {
|
|
178
|
+
// Handle array types
|
|
179
|
+
if (type.endsWith('[]')) {
|
|
180
|
+
if (expr.type === 'ArrayLiteral') {
|
|
181
|
+
const elementType = type.slice(0, -2);
|
|
182
|
+
for (const element of expr.elements) {
|
|
183
|
+
validateLiteralType(ctx, element, elementType, element.location, getExpressionType);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// json type cannot be an array literal - use json[] for arrays
|
|
190
|
+
if (type === 'json' && expr.type === 'ArrayLiteral') {
|
|
191
|
+
ctx.error(`json type expects an object, not an array. Use json[] for arrays.`, location);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Get the source type from the expression
|
|
196
|
+
const sourceType = getExpressionType(expr);
|
|
197
|
+
if (!sourceType) {
|
|
198
|
+
// Can't determine type at compile time
|
|
199
|
+
if (type === 'json' && expr.type === 'StringLiteral') {
|
|
200
|
+
validateJsonLiteral(ctx, expr.value, location);
|
|
201
|
+
}
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Check type compatibility
|
|
206
|
+
if (!typesCompatible(sourceType, type)) {
|
|
207
|
+
ctx.error(`Type error: cannot assign ${sourceType} to ${type}`, location);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Additional JSON validation for string literals
|
|
211
|
+
if (type === 'json' && expr.type === 'StringLiteral') {
|
|
212
|
+
validateJsonLiteral(ctx, expr.value, location);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Validates that an async expression is a single async-capable operation.
|
|
218
|
+
*/
|
|
219
|
+
export function validateAsyncExpression(ctx: AnalyzerContext, expr: AST.Expression, location: SourceLocation): void {
|
|
220
|
+
const validTypes = ['VibeExpression', 'TsBlock', 'CallExpression'];
|
|
221
|
+
if (!validTypes.includes(expr.type)) {
|
|
222
|
+
ctx.error(
|
|
223
|
+
`async declarations require a single do, vibe, ts block, or function call`,
|
|
224
|
+
location
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Validates compress context mode arguments.
|
|
231
|
+
*/
|
|
232
|
+
export function validateContextMode(ctx: AnalyzerContext, mode: AST.ContextMode, location: SourceLocation): void {
|
|
233
|
+
if (mode === 'forget' || mode === 'verbose') return;
|
|
234
|
+
|
|
235
|
+
const { arg1, arg2 } = mode.compress;
|
|
236
|
+
|
|
237
|
+
if (arg1 && arg1.kind === 'identifier') {
|
|
238
|
+
const sym = ctx.symbols.lookup(arg1.name);
|
|
239
|
+
if (!sym) {
|
|
240
|
+
ctx.error(`compress argument '${arg1.name}' is not declared`, location);
|
|
241
|
+
} else if (arg2) {
|
|
242
|
+
const isPromptType = sym.kind === 'constant' && sym.typeAnnotation === 'prompt';
|
|
243
|
+
const isTextType = sym.typeAnnotation === 'text' || sym.typeAnnotation === 'prompt';
|
|
244
|
+
if (!isPromptType && !isTextType) {
|
|
245
|
+
ctx.error(
|
|
246
|
+
`compress first argument '${arg1.name}' must be prompt type when two arguments provided, got ${sym.typeAnnotation ?? sym.kind}`,
|
|
247
|
+
location
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
const isModelType = sym.kind === 'model' || (sym.kind === 'constant' && sym.typeAnnotation === 'model');
|
|
252
|
+
const isPromptType = sym.kind === 'constant' && sym.typeAnnotation === 'prompt';
|
|
253
|
+
const isTextType = sym.typeAnnotation === 'text' || sym.typeAnnotation === 'prompt';
|
|
254
|
+
if (!isModelType && !isPromptType && !isTextType) {
|
|
255
|
+
ctx.error(
|
|
256
|
+
`compress argument '${arg1.name}' must be prompt or model type, got ${sym.typeAnnotation ?? sym.kind}`,
|
|
257
|
+
location
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (arg2 && arg2.kind === 'identifier') {
|
|
264
|
+
const sym = ctx.symbols.lookup(arg2.name);
|
|
265
|
+
if (!sym) {
|
|
266
|
+
ctx.error(`compress model '${arg2.name}' is not declared`, location);
|
|
267
|
+
} else {
|
|
268
|
+
const isModelType = sym.kind === 'model' || (sym.kind === 'constant' && sym.typeAnnotation === 'model');
|
|
269
|
+
if (!isModelType) {
|
|
270
|
+
ctx.error(
|
|
271
|
+
`compress second argument '${arg2.name}' must be model type, got ${sym.typeAnnotation ?? sym.kind}`,
|
|
272
|
+
location
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ============================================================================
|
|
280
|
+
// TypeScript Validation
|
|
281
|
+
// ============================================================================
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Validates a call to an imported TypeScript function.
|
|
285
|
+
*/
|
|
286
|
+
export function validateTsCall(
|
|
287
|
+
ctx: AnalyzerContext,
|
|
288
|
+
node: AST.CallExpression,
|
|
289
|
+
sig: TsFunctionSignature,
|
|
290
|
+
getExpressionType: (expr: AST.Expression) => string | null
|
|
291
|
+
): void {
|
|
292
|
+
// Check argument count
|
|
293
|
+
const requiredParams = sig.params.filter(p => !p.optional).length;
|
|
294
|
+
if (node.arguments.length < requiredParams) {
|
|
295
|
+
ctx.error(
|
|
296
|
+
`Function '${sig.name}' requires ${requiredParams} argument${requiredParams === 1 ? '' : 's'}, got ${node.arguments.length}`,
|
|
297
|
+
node.location
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
if (node.arguments.length > sig.params.length) {
|
|
301
|
+
ctx.error(
|
|
302
|
+
`Function '${sig.name}' accepts at most ${sig.params.length} argument${sig.params.length === 1 ? '' : 's'}, got ${node.arguments.length}`,
|
|
303
|
+
node.location
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Check argument types
|
|
308
|
+
for (let i = 0; i < node.arguments.length && i < sig.params.length; i++) {
|
|
309
|
+
const arg = node.arguments[i];
|
|
310
|
+
const argType = getExpressionType(arg);
|
|
311
|
+
const paramTsType = sig.params[i].tsType;
|
|
312
|
+
|
|
313
|
+
if (!argType) continue;
|
|
314
|
+
|
|
315
|
+
if (!isVibeTypeCompatibleWithTs(argType, paramTsType)) {
|
|
316
|
+
ctx.error(
|
|
317
|
+
`Argument ${i + 1} of '${sig.name}': expected ${paramTsType}, got ${argType}`,
|
|
318
|
+
arg.location
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Validates a ts() block by checking parameter references and type-checking the body.
|
|
326
|
+
*/
|
|
327
|
+
export function validateTsBlock(ctx: AnalyzerContext, node: AST.TsBlock): void {
|
|
328
|
+
const params: Array<{ name: string; vibeType: string | null }> = [];
|
|
329
|
+
for (const paramName of node.params) {
|
|
330
|
+
const symbol = ctx.symbols.lookup(paramName);
|
|
331
|
+
if (!symbol) {
|
|
332
|
+
ctx.error(`'${paramName}' is not defined`, node.location);
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
params.push({
|
|
336
|
+
name: paramName,
|
|
337
|
+
vibeType: symbol.typeAnnotation ?? null,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const tsErrors = checkTsBlockTypes(params, node.body, node.location);
|
|
342
|
+
for (const err of tsErrors) {
|
|
343
|
+
ctx.error(err.message, err.location);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ============================================================================
|
|
348
|
+
// Call Validation
|
|
349
|
+
// ============================================================================
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Check that we're not calling a tool directly.
|
|
353
|
+
*/
|
|
354
|
+
export function checkToolCall(ctx: AnalyzerContext, node: AST.CallExpression): void {
|
|
355
|
+
if (node.callee.type !== 'Identifier') return;
|
|
356
|
+
|
|
357
|
+
const symbol = ctx.symbols.lookup(node.callee.name);
|
|
358
|
+
if (symbol?.kind === 'tool') {
|
|
359
|
+
ctx.error(
|
|
360
|
+
`Cannot call tool '${node.callee.name}' directly. Tools can only be used by AI models via the tools array in model declarations.`,
|
|
361
|
+
node.location
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Check that call arguments match the function's parameter types.
|
|
368
|
+
*/
|
|
369
|
+
export function checkCallArguments(
|
|
370
|
+
ctx: AnalyzerContext,
|
|
371
|
+
node: AST.CallExpression,
|
|
372
|
+
getExpressionType: (expr: AST.Expression) => string | null,
|
|
373
|
+
validateLiteralTypeFn: (expr: AST.Expression, type: string, location: SourceLocation) => void
|
|
374
|
+
): void {
|
|
375
|
+
if (node.callee.type !== 'Identifier') return;
|
|
376
|
+
const calleeName = node.callee.name;
|
|
377
|
+
|
|
378
|
+
// Check if it's a TS import - validate against TS signature
|
|
379
|
+
const tsSig = ctx.tsImportSignatures.get(calleeName);
|
|
380
|
+
if (tsSig) {
|
|
381
|
+
validateTsCall(ctx, node, tsSig, getExpressionType);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Otherwise, check against Vibe function signature
|
|
386
|
+
const funcSymbol = ctx.symbols.lookup(calleeName);
|
|
387
|
+
if (!funcSymbol || funcSymbol.kind !== 'function') return;
|
|
388
|
+
if (!funcSymbol.paramTypes) return;
|
|
389
|
+
|
|
390
|
+
// Check each argument against corresponding parameter type
|
|
391
|
+
for (let i = 0; i < node.arguments.length && i < funcSymbol.paramTypes.length; i++) {
|
|
392
|
+
const arg = node.arguments[i];
|
|
393
|
+
const expectedType = funcSymbol.paramTypes[i];
|
|
394
|
+
if (expectedType) {
|
|
395
|
+
validateLiteralTypeFn(arg, expectedType, arg.location);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// ============================================================================
|
|
401
|
+
// Prompt and Context Validation
|
|
402
|
+
// ============================================================================
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Validates that prompt parameters are string literals or text/prompt typed variables.
|
|
406
|
+
*/
|
|
407
|
+
export function checkPromptType(ctx: AnalyzerContext, node: AST.Expression): void {
|
|
408
|
+
if (node.type !== 'Identifier') return;
|
|
409
|
+
|
|
410
|
+
const sym = ctx.symbols.lookup(node.name);
|
|
411
|
+
if (!sym) return;
|
|
412
|
+
|
|
413
|
+
if (sym.kind === 'model') {
|
|
414
|
+
ctx.error(`Cannot use model '${node.name}' as prompt`, node.location);
|
|
415
|
+
} else if (sym.kind === 'function') {
|
|
416
|
+
ctx.error(`Cannot use function '${node.name}' as prompt`, node.location);
|
|
417
|
+
} else if (sym.typeAnnotation === 'json') {
|
|
418
|
+
ctx.error(`Cannot use json typed variable '${node.name}' as prompt`, node.location);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Validates the model type in a vibe expression.
|
|
424
|
+
*/
|
|
425
|
+
export function checkModelType(
|
|
426
|
+
ctx: AnalyzerContext,
|
|
427
|
+
node: AST.Expression,
|
|
428
|
+
visitExpression: (node: AST.Expression) => void
|
|
429
|
+
): void {
|
|
430
|
+
if (node.type === 'Identifier') {
|
|
431
|
+
const sym = ctx.symbols.lookup(node.name);
|
|
432
|
+
if (!sym) {
|
|
433
|
+
ctx.error(`'${node.name}' is not defined`, node.location);
|
|
434
|
+
} else if (sym.kind !== 'model') {
|
|
435
|
+
const isModelParam = sym.kind === 'parameter' && sym.typeAnnotation === 'model';
|
|
436
|
+
if (!isModelParam) {
|
|
437
|
+
ctx.error(`Expected model, got ${sym.kind} '${node.name}'`, node.location);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
} else {
|
|
441
|
+
visitExpression(node);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Validates the context variable in a vibe expression.
|
|
447
|
+
*/
|
|
448
|
+
export function checkContextVariable(ctx: AnalyzerContext, context: AST.ContextSpecifier): void {
|
|
449
|
+
if (context.kind === 'variable' && context.variable) {
|
|
450
|
+
if (!ctx.symbols.lookup(context.variable)) {
|
|
451
|
+
ctx.error(`'${context.variable}' is not defined`, context.location);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// ============================================================================
|
|
457
|
+
// String Interpolation Validation
|
|
458
|
+
// ============================================================================
|
|
459
|
+
|
|
460
|
+
/** Pattern to match interpolation syntax */
|
|
461
|
+
const INTERPOLATION_PATTERN = /(!?)\{(\w+(?:\.\w+|\[\d+\]|\[\d*:\d*\])*)\}/g;
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Validates string interpolation references.
|
|
465
|
+
*/
|
|
466
|
+
export function validateStringInterpolation(
|
|
467
|
+
ctx: AnalyzerContext,
|
|
468
|
+
value: string,
|
|
469
|
+
isPromptContext: boolean,
|
|
470
|
+
location: SourceLocation
|
|
471
|
+
): void {
|
|
472
|
+
// Skip escaped placeholders
|
|
473
|
+
const testValue = value
|
|
474
|
+
.replace(new RegExp(ESCAPED_LBRACE, 'g'), '')
|
|
475
|
+
.replace(new RegExp(ESCAPED_RBRACE, 'g'), '')
|
|
476
|
+
.replace(new RegExp(ESCAPED_BANG_LBRACE, 'g'), '');
|
|
477
|
+
|
|
478
|
+
// Reset regex state
|
|
479
|
+
INTERPOLATION_PATTERN.lastIndex = 0;
|
|
480
|
+
|
|
481
|
+
let match;
|
|
482
|
+
while ((match = INTERPOLATION_PATTERN.exec(testValue)) !== null) {
|
|
483
|
+
const [, bang, path] = match;
|
|
484
|
+
const isExpansion = bang === '!';
|
|
485
|
+
const varName = path.split(/[.\[]/)[0];
|
|
486
|
+
|
|
487
|
+
const symbol = ctx.symbols.lookup(varName);
|
|
488
|
+
if (!symbol && !isCoreFunction(varName)) {
|
|
489
|
+
ctx.error(`'${varName}' is not defined`, location);
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (!isPromptContext && isExpansion) {
|
|
494
|
+
ctx.error(
|
|
495
|
+
`Expansion syntax !{${path}} is only valid in prompt strings (do/vibe expressions or prompt-typed variables)`,
|
|
496
|
+
location
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// ============================================================================
|
|
503
|
+
// Expression Type Inference
|
|
504
|
+
// ============================================================================
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Gets the type of an expression if it can be determined at compile time.
|
|
508
|
+
*/
|
|
509
|
+
export function getExpressionType(ctx: AnalyzerContext, expr: AST.Expression): string | null {
|
|
510
|
+
switch (expr.type) {
|
|
511
|
+
case 'StringLiteral':
|
|
512
|
+
case 'TemplateLiteral':
|
|
513
|
+
return 'text';
|
|
514
|
+
case 'BooleanLiteral':
|
|
515
|
+
return 'boolean';
|
|
516
|
+
case 'NumberLiteral':
|
|
517
|
+
return 'number';
|
|
518
|
+
case 'NullLiteral':
|
|
519
|
+
return 'null';
|
|
520
|
+
case 'ObjectLiteral':
|
|
521
|
+
return 'json';
|
|
522
|
+
case 'ArrayLiteral': {
|
|
523
|
+
// Infer array type from first element
|
|
524
|
+
if (expr.elements.length === 0) {
|
|
525
|
+
return null; // Empty array - type unknown
|
|
526
|
+
}
|
|
527
|
+
const firstElementType = getExpressionType(ctx, expr.elements[0]);
|
|
528
|
+
if (firstElementType && !firstElementType.endsWith('[]')) {
|
|
529
|
+
return `${firstElementType}[]`;
|
|
530
|
+
}
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
case 'SliceExpression': {
|
|
534
|
+
// A slice of an array has the same type as the array
|
|
535
|
+
const objectType = getExpressionType(ctx, expr.object);
|
|
536
|
+
if (objectType?.endsWith('[]')) {
|
|
537
|
+
return objectType;
|
|
538
|
+
}
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
case 'Identifier': {
|
|
542
|
+
const symbol = ctx.symbols.lookup(expr.name);
|
|
543
|
+
if (symbol?.typeAnnotation) {
|
|
544
|
+
return symbol.typeAnnotation;
|
|
545
|
+
}
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
case 'CallExpression': {
|
|
549
|
+
if (expr.callee.type === 'Identifier') {
|
|
550
|
+
const tsSig = ctx.tsImportSignatures.get(expr.callee.name);
|
|
551
|
+
if (tsSig) {
|
|
552
|
+
return tsTypeToVibe(tsSig.returnType);
|
|
553
|
+
}
|
|
554
|
+
const funcSymbol = ctx.symbols.lookup(expr.callee.name);
|
|
555
|
+
if (funcSymbol?.kind === 'function' && funcSymbol.returnType) {
|
|
556
|
+
return funcSymbol.returnType;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
case 'TsBlock': {
|
|
562
|
+
const params: Array<{ name: string; vibeType: string | null }> = [];
|
|
563
|
+
for (const paramName of expr.params) {
|
|
564
|
+
const symbol = ctx.symbols.lookup(paramName);
|
|
565
|
+
params.push({
|
|
566
|
+
name: paramName,
|
|
567
|
+
vibeType: symbol?.typeAnnotation ?? null,
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
return inferTsBlockReturnType(params, expr.body);
|
|
571
|
+
}
|
|
572
|
+
default:
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
}
|