UNPKG

meld

Version:

Meld: A template language for LLM prompts

280 lines (251 loc) 7.95 kB
import { DirectiveNode, ImportDirectiveData } 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'; // Local definition for StructuredPath interface interface StructuredPath { raw: string; normalized?: string; structured?: { base?: string; segments?: string[]; variables?: { special?: string[]; [key: string]: any; }; }; } // Definition for Import item from meld-ast 3.4.0 interface ImportItem { name: string; alias?: string; } /** * Convert AST SourceLocation to DirectiveLocation */ function convertLocation(location: any): DirectiveLocation | undefined { if (!location) return undefined; return { line: location.start?.line || 0, column: location.start?.column || 0, filePath: location.filePath }; } /** * Validates @import directives */ export function validateImportDirective(node: DirectiveNode): void { const directive = node.directive as ImportDirectiveData; // Validate that a path exists if (!directive.path) { throw new MeldDirectiveError( 'Import directive requires a path', 'import', { location: convertLocation(node.location), code: DirectiveErrorCode.VALIDATION_FAILED, severity: ErrorSeverity.Fatal } ); } // Get path value from structured path or string let pathValue: string; if (typeof directive.path === 'string') { pathValue = directive.path; } else if (directive.path.raw) { pathValue = directive.path.raw; } else if (directive.path.normalized) { pathValue = directive.path.normalized; } else { throw new MeldDirectiveError( 'Import directive has an invalid path format', 'import', { location: convertLocation(node.location), code: DirectiveErrorCode.VALIDATION_FAILED, severity: ErrorSeverity.Fatal } ); } // Validate path is not empty if (pathValue.trim() === '') { throw new MeldDirectiveError( 'Import path cannot be empty', 'import', { location: convertLocation(node.location), code: DirectiveErrorCode.VALIDATION_FAILED, severity: ErrorSeverity.Fatal } ); } // Allow path variables starting with $ but still validate .. if (!pathValue.startsWith('$') && pathValue.includes('..')) { throw new MeldDirectiveError( 'Import path cannot contain parent directory references (..) unless using a path variable', 'import', { location: convertLocation(node.location), code: DirectiveErrorCode.VALIDATION_FAILED, severity: ErrorSeverity.Fatal } ); } // Validate imports - use structured imports array if available, otherwise use importList if (directive.imports && Array.isArray(directive.imports)) { validateStructuredImports(directive.imports, node); } else if (directive.importList && directive.importList !== '*') { validateImportList(directive.importList, node); } } /** * Validates structured imports array from meld-ast 3.4.0 * @private */ function validateStructuredImports(imports: ImportItem[], node: DirectiveNode): void { if (imports.length === 0) { // Empty imports array is valid - equivalent to "*" return; } // Check if there's a wildcard import if (imports.some(imp => imp.name === '*')) { if (imports.length > 1) { throw new MeldDirectiveError( 'Wildcard import (*) cannot be combined with other imports', 'import', { location: convertLocation(node.location), code: DirectiveErrorCode.VALIDATION_FAILED, severity: ErrorSeverity.Fatal } ); } return; // Single wildcard import is valid } // Validate each import item for (const item of imports) { if (!item.name || item.name.trim() === '') { throw new MeldDirectiveError( 'Import identifier cannot be empty', 'import', { location: convertLocation(node.location), code: DirectiveErrorCode.VALIDATION_FAILED, severity: ErrorSeverity.Fatal } ); } // Only validate the alias if it's a non-undefined, non-null string // This allows for cases where alias is undefined (no alias specified) if (item.alias !== undefined && item.alias !== null && item.alias.trim() === '') { throw new MeldDirectiveError( 'Import alias cannot be empty', 'import', { location: convertLocation(node.location), code: DirectiveErrorCode.VALIDATION_FAILED, severity: ErrorSeverity.Fatal } ); } } } /** * Validates import list * @private */ function validateImportList(importList: string, node: DirectiveNode): void { if (importList === '*') { return; // Wildcard import is valid } // Remove brackets if present using direct string manipulation let cleanList = importList; if (cleanList.startsWith('[') && cleanList.endsWith(']')) { cleanList = cleanList.substring(1, cleanList.length - 1); } // Split by commas and validate each part const parts = cleanList.split(','); for (const part of parts) { const trimmedPart = part.trim(); if (trimmedPart === '') { throw new MeldDirectiveError( 'Import list contains an empty item', 'import', { location: convertLocation(node.location), code: DirectiveErrorCode.VALIDATION_FAILED, severity: ErrorSeverity.Fatal } ); } // Handle colon syntax (var:alias) if (trimmedPart.includes(':')) { const [name, alias] = trimmedPart.split(':').map(s => s.trim()); if (!name || name === '') { throw new MeldDirectiveError( 'Import identifier cannot be empty', 'import', { location: convertLocation(node.location), code: DirectiveErrorCode.VALIDATION_FAILED, severity: ErrorSeverity.Fatal } ); } if (!alias || alias === '') { throw new MeldDirectiveError( 'Import alias cannot be empty', 'import', { location: convertLocation(node.location), code: DirectiveErrorCode.VALIDATION_FAILED, severity: ErrorSeverity.Fatal } ); } continue; } // Handle 'as' syntax (var as alias) without regex const asIndex = trimmedPart.indexOf(' as '); if (asIndex !== -1) { const name = trimmedPart.substring(0, asIndex).trim(); const alias = trimmedPart.substring(asIndex + 4).trim(); if (!name || name === '') { throw new MeldDirectiveError( 'Import identifier cannot be empty', 'import', { location: convertLocation(node.location), code: DirectiveErrorCode.VALIDATION_FAILED, severity: ErrorSeverity.Fatal } ); } // When using 'as' syntax, the alias must not be empty if (!alias || alias === '') { throw new MeldDirectiveError( 'Import alias cannot be empty', 'import', { location: convertLocation(node.location), code: DirectiveErrorCode.VALIDATION_FAILED, severity: ErrorSeverity.Fatal } ); } continue; } // Simple import without alias if (trimmedPart === '') { throw new MeldDirectiveError( 'Import identifier cannot be empty', 'import', { location: convertLocation(node.location), code: DirectiveErrorCode.VALIDATION_FAILED, severity: ErrorSeverity.Fatal } ); } } }