UNPKG

meld

Version:

Meld: A template language for LLM prompts

156 lines (142 loc) 5.05 kB
import { DirectiveNode, PathDirectiveData } from 'meld-spec'; import { MeldDirectiveError, DirectiveLocation } from '@core/errors/MeldDirectiveError.js'; import { DirectiveErrorCode } from '@services/pipeline/DirectiveService/errors/DirectiveError.js'; import { ErrorSeverity } from '@core/errors/MeldError.js'; import { ResolutionContext } from '@services/resolution/ResolutionService/IResolutionService.js'; /** * Converts AST SourceLocation to DirectiveLocation */ function convertLocation(location: any): DirectiveLocation { if (!location) return { line: 0, column: 0 }; return { line: location.line, column: location.column }; } /** * Validates path directives based on the latest meld-ast structure * Uses AST-based validation instead of regex */ export async function validatePathDirective(node: DirectiveNode, context?: ResolutionContext): Promise<void> { if (!node.directive) { throw new MeldDirectiveError( 'Path directive is missing required fields', 'path', { location: convertLocation(node.location?.start), code: DirectiveErrorCode.VALIDATION_FAILED, severity: ErrorSeverity.Fatal } ); } // Cast to PathDirectiveData to access typed properties const directive = node.directive as PathDirectiveData; // Fix for different field names: AST can use either 'id' or 'identifier' const identifier = directive.identifier || (directive as any).id; // Check for required fields if (!identifier || typeof identifier !== 'string' || identifier.trim() === '') { throw new MeldDirectiveError( 'Path directive requires a valid identifier', 'path', { location: convertLocation(node.location?.start), code: DirectiveErrorCode.VALIDATION_FAILED, severity: ErrorSeverity.Fatal } ); } // Validate identifier format using character-by-character validation // instead of regex if (!isValidIdentifier(identifier)) { throw new MeldDirectiveError( 'Path identifier must be a valid identifier (letters, numbers, underscore, starting with letter/underscore)', 'path', { location: convertLocation(node.location?.start), code: DirectiveErrorCode.VALIDATION_FAILED, severity: ErrorSeverity.Fatal } ); } // Handle both direct string value and path object let pathObject = directive.path; let pathRaw: string | undefined; if (!pathObject) { // If path is missing, check for value property as fallback if (directive.value) { pathRaw = typeof directive.value === 'string' ? directive.value : (directive.value as any).raw || ''; } else { throw new MeldDirectiveError( 'Path directive requires a path value', 'path', { location: convertLocation(node.location?.start), code: DirectiveErrorCode.VALIDATION_FAILED, severity: ErrorSeverity.Fatal } ); } } else if (typeof pathObject === 'string') { // Handle direct string path pathRaw = pathObject; } else if (typeof pathObject === 'object') { // Handle path object with raw property if (!pathObject.raw || typeof pathObject.raw !== 'string' || pathObject.raw.trim() === '') { throw new MeldDirectiveError( 'Path directive requires a non-empty path value', 'path', { location: convertLocation(node.location?.start), code: DirectiveErrorCode.VALIDATION_FAILED, severity: ErrorSeverity.Fatal } ); } pathRaw = pathObject.raw; } else { throw new MeldDirectiveError( 'Path directive requires a valid path', 'path', { location: convertLocation(node.location?.start), code: DirectiveErrorCode.VALIDATION_FAILED, severity: ErrorSeverity.Fatal } ); } // Ensure we have a non-empty path if (!pathRaw || pathRaw.trim() === '') { throw new MeldDirectiveError( 'Path directive requires a non-empty path value', 'path', { location: convertLocation(node.location?.start), code: DirectiveErrorCode.VALIDATION_FAILED, severity: ErrorSeverity.Fatal } ); } // Path validation (absolute paths, path segments) is handled by ParserService } /** * Helper function to validate identifier format without regex */ function isValidIdentifier(str: string): boolean { if (!str || str.length === 0) return false; // First character must be letter or underscore const firstChar = str.charAt(0); if (!(firstChar === '_' || (firstChar >= 'a' && firstChar <= 'z') || (firstChar >= 'A' && firstChar <= 'Z'))) { return false; } // Rest of characters must be letters, numbers, or underscore for (let i = 1; i < str.length; i++) { const char = str.charAt(i); if (!((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9') || char === '_')) { return false; } } return true; }