UNPKG

mlld

Version:

mlld: a modular prompt scripting language

655 lines (649 loc) 23.4 kB
import { logger } from './chunk-XGMRAGIT.mjs'; import { init_locationFormatter, locationFormatter_exports } from './chunk-OIIQ3NPY.mjs'; import { __name, __publicField, __toCommonJS } from './chunk-OMKLS24H.mjs'; import chalk from 'chalk'; import * as path from 'path'; // core/utils/sourceContextExtractor.ts var _SourceContextExtractor = class _SourceContextExtractor { constructor(fileSystem) { __publicField(this, "fileSystem"); __publicField(this, "fileCache"); __publicField(this, "maxCacheSize"); __publicField(this, "cacheTtl"); this.fileSystem = fileSystem; this.fileCache = /* @__PURE__ */ new Map(); this.maxCacheSize = 100; this.cacheTtl = 6e4; } async extractContext(location, options = {}) { const { contextLines = 2, maxLineLength = 120 } = options; if (!location.file || !location.line) { return null; } try { const lines = await this.getFileLines(location.file); return this.extractContextFromLines(lines, location, { contextLines, maxLineLength }); } catch (error) { return null; } } /** * Extract context from provided source content instead of reading from file */ extractContextFromSource(sourceContent, location, options = {}) { if (!location.line) { return null; } const lines = sourceContent.split("\n"); return this.extractContextFromLines(lines, location, options); } extractContextFromLines(lines, location, options = {}) { const { contextLines = 2, maxLineLength = 120 } = options; const errorLineIndex = location.line - 1; if (errorLineIndex < 0 || errorLineIndex >= lines.length) { return null; } const startLine = Math.max(0, errorLineIndex - contextLines); const endLine = Math.min(lines.length - 1, errorLineIndex + contextLines); const contextLines_ = []; for (let i = startLine; i <= endLine; i++) { let content = lines[i]; if (content.length > maxLineLength) { const column = location.column || 1; const start = Math.max(0, column - 40); const end = Math.min(content.length, start + maxLineLength); content = (start > 0 ? "..." : "") + content.slice(start, end) + (end < content.length ? "..." : ""); } contextLines_.push({ number: i + 1, content, isErrorLine: i === errorLineIndex }); } return { file: location.file, lines: contextLines_, errorLine: location.line, errorColumn: location.column || 1 }; } async getFileLines(filePath) { const now = Date.now(); const cached = this.fileCache.get(filePath); if (cached && now - cached.timestamp < this.cacheTtl) { return cached.lines; } try { const content = await this.fileSystem.readFile(filePath); const lines = content.split("\n"); if (this.fileCache.size >= this.maxCacheSize) { this.cleanCache(); } this.fileCache.set(filePath, { lines, timestamp: now }); return lines; } catch (error) { throw error; } } cleanCache() { const now = Date.now(); const entries = Array.from(this.fileCache.entries()); const expiredKeys = entries.filter(([_, cache]) => now - cache.timestamp >= this.cacheTtl).map(([key]) => key); expiredKeys.forEach((key) => this.fileCache.delete(key)); if (this.fileCache.size >= this.maxCacheSize) { const sortedEntries = entries.filter(([key]) => !expiredKeys.includes(key)).sort(([_, a], [__, b]) => a.timestamp - b.timestamp); const toRemove = sortedEntries.slice(0, sortedEntries.length - this.maxCacheSize + 10); toRemove.forEach(([key]) => this.fileCache.delete(key)); } } clearCache() { this.fileCache.clear(); } }; __name(_SourceContextExtractor, "SourceContextExtractor"); var SourceContextExtractor = _SourceContextExtractor; var _SmartPathResolver = class _SmartPathResolver { constructor(fileSystem) { __publicField(this, "fileSystem"); __publicField(this, "projectRootCache", /* @__PURE__ */ new Map()); this.fileSystem = fileSystem; } /** * Resolve a file path to the most appropriate display format */ async resolvePath(filePath, options = {}) { const { basePath = process.cwd(), workingDirectory = process.cwd(), preferRelative = true, maxRelativeDepth = 3 } = options; const absolutePath = path.resolve(filePath); const projectRoot = await this.findProjectRoot(absolutePath, basePath); const relativeFromCwd = path.relative(workingDirectory, absolutePath); const relativeFromProject = projectRoot ? path.relative(projectRoot, absolutePath) : relativeFromCwd; const isWithinProject = projectRoot !== null && !relativeFromProject.startsWith(".."); let displayPath; let isRelative = false; if (preferRelative && isWithinProject) { const projectRelative = relativeFromProject.startsWith("./") ? relativeFromProject : `./${relativeFromProject}`; const upLevels = (projectRelative.match(/\.\.\//g) || []).length; if (upLevels <= maxRelativeDepth) { displayPath = projectRelative; isRelative = true; } else { displayPath = absolutePath; } } else if (preferRelative && !relativeFromCwd.startsWith("..")) { const upLevels = (relativeFromCwd.match(/\.\.\//g) || []).length; if (upLevels <= maxRelativeDepth) { displayPath = relativeFromCwd.startsWith("./") ? relativeFromCwd : `./${relativeFromCwd}`; isRelative = true; } else { displayPath = absolutePath; } } else { displayPath = absolutePath; } return { display: displayPath, absolute: absolutePath, relative: isWithinProject ? relativeFromProject : relativeFromCwd, isRelative, isWithinProject }; } /** * Find the project root by looking for package.json, .git, or other indicators */ async findProjectRoot(startPath, fallbackBasePath) { const cacheKey = startPath; if (this.projectRootCache.has(cacheKey)) { return this.projectRootCache.get(cacheKey) || null; } let currentDir = path.dirname(startPath); const rootDir = path.parse(currentDir).root; while (currentDir !== rootDir) { const indicators = [ "package.json", ".git", "pyproject.toml", "Cargo.toml", "go.mod", "composer.json", "pom.xml", "build.gradle", "Makefile", "README.md" ]; for (const indicator of indicators) { const indicatorPath = path.join(currentDir, indicator); try { if (await this.fileExists(indicatorPath)) { this.projectRootCache.set(cacheKey, currentDir); return currentDir; } } catch { } } currentDir = path.dirname(currentDir); } this.projectRootCache.set(cacheKey, fallbackBasePath); return fallbackBasePath; } /** * Check if a file exists */ async fileExists(filePath) { try { await this.fileSystem.readFile(filePath); return true; } catch { return false; } } /** * Clear the project root cache */ clearCache() { this.projectRootCache.clear(); } /** * Format a file path for error display with line and column */ formatPathForDisplay(resolvedPath, line, column) { let result = resolvedPath.display; if (line !== void 0) { result += `:${line}`; if (column !== void 0) { result += `:${column}`; } } return result; } }; __name(_SmartPathResolver, "SmartPathResolver"); var SmartPathResolver = _SmartPathResolver; // core/utils/enhancedLocationFormatter.ts var _EnhancedLocationFormatter = class _EnhancedLocationFormatter { constructor(fileSystem) { __publicField(this, "pathResolver"); this.pathResolver = new SmartPathResolver(fileSystem); } async formatLocation(location, options = {}) { const { useSmartPaths = true, ...pathOptions } = options; if (!location) { return { display: "unknown location" }; } if ("start" in location && location.start) { const startPos = location.start; const filePath = startPos.filePath || location.filePath; if (filePath) { let displayPath = filePath; let isRelative = false; let isWithinProject = false; if (useSmartPaths) { try { const resolvedPath = await this.pathResolver.resolvePath(filePath, pathOptions); displayPath = resolvedPath.display; isRelative = resolvedPath.isRelative; isWithinProject = resolvedPath.isWithinProject; } catch { displayPath = filePath; } } const parts = [ displayPath ]; if (startPos.line !== void 0) { if (startPos.column !== void 0) { parts.push(`${startPos.line}:${startPos.column}`); } else { parts.push(`line ${startPos.line}`); } } return { display: parts.join(":"), file: filePath, displayPath, line: startPos.line, column: startPos.column, isRelative, isWithinProject }; } } if ("filePath" in location && location.filePath) { let displayPath = location.filePath; let isRelative = false; let isWithinProject = false; if (useSmartPaths) { try { const resolvedPath = await this.pathResolver.resolvePath(location.filePath, pathOptions); displayPath = resolvedPath.display; isRelative = resolvedPath.isRelative; isWithinProject = resolvedPath.isWithinProject; } catch { displayPath = location.filePath; } } const parts = [ displayPath ]; if (location.line !== void 0) { if (location.column !== void 0) { parts.push(`${location.line}:${location.column}`); } else { parts.push(`line ${location.line}`); } } return { display: parts.join(":"), file: location.filePath, displayPath, line: location.line, column: location.column, isRelative, isWithinProject }; } if ("line" in location && location.line !== void 0) { const parts = []; if (location.column !== void 0) { parts.push(`line ${location.line}, column ${location.column}`); } else { parts.push(`line ${location.line}`); } return { display: parts.join(""), line: location.line, column: location.column }; } return { display: "unknown location" }; } async formatLocationForError(location, options = {}) { const formatted = await this.formatLocation(location, options); return formatted.display; } clearCache() { this.pathResolver.clearCache(); } }; __name(_EnhancedLocationFormatter, "EnhancedLocationFormatter"); var EnhancedLocationFormatter = _EnhancedLocationFormatter; // core/utils/errorDisplayFormatter.ts var _ErrorDisplayFormatter = class _ErrorDisplayFormatter { constructor(fileSystem) { __publicField(this, "sourceExtractor"); __publicField(this, "locationFormatter"); this.sourceExtractor = new SourceContextExtractor(fileSystem); this.locationFormatter = new EnhancedLocationFormatter(fileSystem); } async formatError(error, options = {}) { const { showSourceContext = true, contextLines = 2, maxLineLength = 120, useColors = true, useSmartPaths = true, basePath, workingDirectory = process.cwd() } = options; const parts = []; const errorHeader = this.formatErrorHeader(error, useColors); parts.push(errorHeader); if (error.details?.peggyFormatted) { const parseErrorIndicator = useColors ? chalk.red.bold("\u2718 Parse Error") : "\u2718 Parse Error"; parts.push("\n" + parseErrorIndicator); const sourceSection = this.extractPeggySourceSection(error.details.peggyFormatted, useColors); if (sourceSection) { parts.push(sourceSection); } } if (showSourceContext && error.sourceLocation && !error.details?.peggyFormatted) { logger.debug("[ErrorDisplay] Formatting source context:", { sourceLocation: error.sourceLocation, errorDetails: error.details, hasFile: !!error.sourceLocation.filePath || !!error.details?.filePath }); const formattedLocation = await this.locationFormatter.formatLocation(error.sourceLocation, { useSmartPaths, basePath, workingDirectory, preferRelative: true, maxRelativeDepth: 3 }); logger.debug("[ErrorDisplay] Formatted location:", formattedLocation); const sourceContent = error.sourceContent || error.details?.sourceContent; if (sourceContent) { const sourceContext = this.sourceExtractor.extractContextFromSource(sourceContent, { display: formattedLocation.display, file: formattedLocation.displayPath || formattedLocation.file || "<stdin>", line: formattedLocation.line, column: formattedLocation.column }, { contextLines, maxLineLength }); logger.debug("[ErrorDisplay] Source context from content:", { hasContext: !!sourceContext, sourceLength: sourceContent.length, lines: sourceContext?.lines?.length }); if (sourceContext) { const contextDisplay = this.formatSourceContext(sourceContext, useColors, error.details?.mlldLocation); parts.push(contextDisplay); } } else if (formattedLocation.file) { const sourceContext = await this.sourceExtractor.extractContext({ display: formattedLocation.display, file: formattedLocation.file, line: formattedLocation.line, column: formattedLocation.column }, { contextLines, maxLineLength }); logger.debug("[ErrorDisplay] Source context extracted:", { hasContext: !!sourceContext, file: formattedLocation.file, lines: sourceContext?.lines?.length }); if (sourceContext) { const enhancedSourceContext = { ...sourceContext, file: formattedLocation.displayPath || formattedLocation.file // Use smart path for display }; const contextDisplay = this.formatSourceContext(enhancedSourceContext, useColors, error.details?.mlldLocation); parts.push(contextDisplay); } } else { logger.debug("[ErrorDisplay] No file path in formatted location and no source content"); } } else { logger.debug("[ErrorDisplay] No source context requested or no sourceLocation:", { showSourceContext, hasSourceLocation: !!error.sourceLocation }); } const detailsDisplay = await this.formatErrorDetails(error, useColors, { useSmartPaths, basePath, workingDirectory }); if (detailsDisplay) { parts.push(detailsDisplay); } if (error.details?.directiveTrace && error.details.directiveTrace.length > 0) { const { DirectiveTraceFormatter } = await import('./DirectiveTraceFormatter-EHF7LN6H.mjs'); const traceFormatter = new DirectiveTraceFormatter(); const trace = traceFormatter.format(error.details.directiveTrace, useColors); parts.push("\n" + trace); } if (error.details?.suggestion) { const suggestion = useColors ? chalk.cyan(`\u{1F4A1} ${error.details.suggestion}`) : `\u{1F4A1} ${error.details.suggestion}`; parts.push(suggestion); } return parts.join("\n\n"); } formatErrorHeader(error, useColors) { const errorName = error.name.replace("Error", ""); if (useColors) { return chalk.red.bold(`${errorName}: ${error.message}`); } return `${errorName}: ${error.message}`; } formatSourceContext(context, useColors, mlldLocation) { const parts = []; if (context.file) { const fileHeader = useColors ? chalk.blue.bold(` ${context.file}:${context.errorLine}:${context.errorColumn}`) : ` ${context.file}:${context.errorLine}:${context.errorColumn}`; parts.push(fileHeader); } const maxLineNum = Math.max(...context.lines.map((l) => l.number)); const lineNumWidth = String(maxLineNum).length; for (const line of context.lines) { const lineNum = String(line.number).padStart(lineNumWidth, " "); const prefix = ` ${lineNum} | `; if (line.isErrorLine) { const errorLineDisplay = useColors ? chalk.red(`${prefix}${line.content}`) : `${prefix}${line.content}`; parts.push(errorLineDisplay); const indicator = this.createErrorIndicator(prefix.length, context.errorColumn, line.content, useColors, mlldLocation); if (indicator) { parts.push(indicator); } } else { const contextLineDisplay = useColors ? chalk.gray(`${prefix}${line.content}`) : `${prefix}${line.content}`; parts.push(contextLineDisplay); } } return parts.join("\n"); } createErrorIndicator(prefixLength, column, lineContent, useColors, mlldLocation) { if (column < 1) return null; if (mlldLocation && mlldLocation.column && mlldLocation.length) { const actualColumn = mlldLocation.column; const errorLength = mlldLocation.length || 1; const spaces2 = " ".repeat(prefixLength + actualColumn - 1); const arrows = "^".repeat(Math.min(errorLength, lineContent.length - actualColumn + 1)); let hint = ""; if (mlldLocation.expectedToken) { hint = ` Expected: ${mlldLocation.expectedToken}`; } return useColors ? `${spaces2}${chalk.red.bold(arrows)}${chalk.yellow(hint)}` : `${spaces2}${arrows}${hint}`; } const spaces = " ".repeat(prefixLength + column - 1); const indicator = "^"; return useColors ? `${spaces}${chalk.red.bold(indicator)}` : `${spaces}${indicator}`; } extractPeggySourceSection(peggyFormatted, useColors) { const lines = peggyFormatted.split("\n"); const sourceLines = []; let inSourceSection = false; for (const line of lines) { if (line.trim().startsWith("-->")) { sourceLines.push(line); inSourceSection = true; continue; } if (inSourceSection) { if (line === "" && sourceLines.length > 2 && sourceLines[sourceLines.length - 1].includes("^")) { break; } sourceLines.push(line); } } return sourceLines.length > 0 ? sourceLines.join("\n") : null; } async formatErrorDetails(error, useColors, pathOptions) { if (!error.details || typeof error.details !== "object") { return null; } const relevantDetails = []; for (const [key, value] of Object.entries(error.details)) { if (key === "suggestion") continue; if (key === "sourceContent") continue; if (key === "peggyFormatted") continue; if (key === "mlldLocation") continue; if (value === void 0 || value === null) continue; if (value && typeof value === "object" && ("line" in value || "filePath" in value)) { try { const formattedLocation = await this.locationFormatter.formatLocationForError(value, pathOptions); relevantDetails.push(` ${key}: ${formattedLocation}`); } catch { const { formatLocationForError } = (init_locationFormatter(), __toCommonJS(locationFormatter_exports)); relevantDetails.push(` ${key}: ${formatLocationForError(value)}`); } } else { relevantDetails.push(` ${key}: ${String(value)}`); } } if (relevantDetails.length === 0) { return null; } const header = useColors ? chalk.gray("Details:") : "Details:"; const details = useColors ? chalk.gray(relevantDetails.join("\n")) : relevantDetails.join("\n"); return `${header} ${details}`; } }; __name(_ErrorDisplayFormatter, "ErrorDisplayFormatter"); var ErrorDisplayFormatter = _ErrorDisplayFormatter; // core/utils/errorFormatSelector.ts var _ErrorFormatSelector = class _ErrorFormatSelector { constructor(fileSystem) { __publicField(this, "fileSystem"); __publicField(this, "formatter"); this.fileSystem = fileSystem; if (fileSystem) { this.formatter = new ErrorDisplayFormatter(fileSystem); } } /** * Format error for CLI display with colors and context */ async formatForCLI(error, options = {}) { if (!this.formatter) { return this.formatForAPI(error, options).formatted; } return await this.formatter.formatError(error, { showSourceContext: options.useSourceContext ?? true, useColors: options.useColors ?? true, useSmartPaths: options.useSmartPaths ?? true, basePath: options.basePath, workingDirectory: options.workingDirectory || process.cwd(), contextLines: options.contextLines || 2 }); } /** * Format error for API usage (no colors, structured data) */ formatForAPI(error, options = {}) { const json = { name: error.name, message: error.message, code: error.code, severity: error.severity, sourceLocation: error.sourceLocation, details: error.details, cause: error.cause instanceof Error ? { name: error.cause.name, message: error.cause.message } : error.cause }; let formatted = `${error.name}: ${error.message}`; if (error.sourceLocation) { if ("filePath" in error.sourceLocation && error.sourceLocation.filePath) { let location = error.sourceLocation.filePath; if (error.sourceLocation.line) { location += `:${error.sourceLocation.line}`; if (error.sourceLocation.column) { location += `:${error.sourceLocation.column}`; } } formatted += ` at ${location}`; } else if ("line" in error.sourceLocation && error.sourceLocation.line) { formatted += ` at line ${error.sourceLocation.line}`; if (error.sourceLocation.column) { formatted += `, column ${error.sourceLocation.column}`; } } } if (error.details?.suggestion) { formatted += ` Suggestion: ${error.details.suggestion}`; } return { formatted, raw: error, json }; } /** * Auto-detect appropriate format based on environment */ async formatAuto(error, options = {}) { const isTTY = process.stdout?.isTTY ?? false; const hasColors = options.useColors ?? isTTY; const hasContext = options.useSourceContext ?? isTTY; if (hasColors && hasContext && this.formatter) { const formatted = await this.formatForCLI(error, { ...options, useColors: hasColors, useSourceContext: hasContext }); return { formatted, raw: error, json: error.toJSON() }; } else { return this.formatForAPI(error, options); } } }; __name(_ErrorFormatSelector, "ErrorFormatSelector"); var ErrorFormatSelector = _ErrorFormatSelector; export { ErrorFormatSelector }; //# sourceMappingURL=chunk-JO67PGXR.mjs.map //# sourceMappingURL=chunk-JO67PGXR.mjs.map