UNPKG

@ai-capabilities-suite/mcp-debugger-core

Version:

Core debugging engine for Node.js and TypeScript applications. Provides Inspector Protocol integration, breakpoint management, variable inspection, execution control, profiling, hang detection, and source map support.

333 lines 12.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.SourceMapManager = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const source_map_1 = require("source-map"); /** * Manages source maps for a debug session * Handles loading, parsing, and caching of source maps * Requirements: 7.1 */ class SourceMapManager { constructor() { this.sourceMapCache = new Map(); this.sourceMapPromises = new Map(); } /** * Load and parse a source map for a JavaScript file * Detects .map files alongside JavaScript files * Caches loaded source maps per session * @param jsFile Path to the JavaScript file * @returns SourceMapConsumer or null if no source map found */ async loadSourceMap(jsFile) { // Check cache first if (this.sourceMapCache.has(jsFile)) { return this.sourceMapCache.get(jsFile); } // Check if we're already loading this source map if (this.sourceMapPromises.has(jsFile)) { return this.sourceMapPromises.get(jsFile); } // Start loading the source map const loadPromise = this.loadSourceMapInternal(jsFile); this.sourceMapPromises.set(jsFile, loadPromise); try { const consumer = await loadPromise; if (consumer) { this.sourceMapCache.set(jsFile, consumer); } return consumer; } finally { this.sourceMapPromises.delete(jsFile); } } /** * Internal method to load and parse a source map */ async loadSourceMapInternal(jsFile) { try { // Try to find the .map file const mapFile = `${jsFile}.map`; if (!fs.existsSync(mapFile)) { // No source map file found return null; } // Read and parse the source map const mapContent = fs.readFileSync(mapFile, "utf8"); const rawSourceMap = JSON.parse(mapContent); // Create a SourceMapConsumer const consumer = await new source_map_1.SourceMapConsumer(rawSourceMap); return consumer; } catch (error) { // Failed to load or parse source map // This is not a critical error - we can still debug without source maps return null; } } /** * Check if a source map exists for a JavaScript file * @param jsFile Path to the JavaScript file * @returns True if a source map exists */ hasSourceMap(jsFile) { if (this.sourceMapCache.has(jsFile)) { return true; } const mapFile = `${jsFile}.map`; return fs.existsSync(mapFile); } /** * Get a cached source map consumer * @param jsFile Path to the JavaScript file * @returns SourceMapConsumer or undefined if not cached */ getCachedSourceMap(jsFile) { return this.sourceMapCache.get(jsFile); } /** * Clear all cached source maps * Should be called when the session ends */ clearCache() { // Destroy all consumers to free memory for (const consumer of this.sourceMapCache.values()) { consumer.destroy(); } this.sourceMapCache.clear(); this.sourceMapPromises.clear(); } /** * Get the number of cached source maps */ getCacheSize() { return this.sourceMapCache.size; } /** * Map a TypeScript source location to JavaScript compiled location * Used when setting breakpoints in TypeScript files * Requirements: 7.2 * @param sourceLocation Original TypeScript location * @returns Compiled JavaScript location or null if mapping fails */ async mapSourceToCompiled(sourceLocation) { try { // We need to find the JavaScript file that corresponds to this TypeScript file // Try common patterns: .ts -> .js, .tsx -> .jsx const jsFile = this.findCompiledFile(sourceLocation.file); if (!jsFile) { return null; } // Load the source map for the JavaScript file const consumer = await this.loadSourceMap(jsFile); if (!consumer) { // No source map available - return null to indicate we can't map return null; } // Source maps use 1-based line numbers and 0-based column numbers // We need to find all generated positions for this source location const generatedPositions = []; consumer.eachMapping((mapping) => { // Check if this mapping corresponds to our source location if (mapping.source && mapping.source.endsWith(path.basename(sourceLocation.file)) && mapping.originalLine === sourceLocation.line) { generatedPositions.push({ line: mapping.generatedLine, column: mapping.generatedColumn, }); } }); if (generatedPositions.length === 0) { // No mapping found for this location return null; } // Use the first generated position (usually the most relevant) const generated = generatedPositions[0]; return { file: jsFile, line: generated.line, column: generated.column, }; } catch (error) { // Handle missing or invalid source maps gracefully return null; } } /** * Map a JavaScript compiled location back to TypeScript source location * Used when the debugger pauses to show the original source location * Requirements: 7.3 * @param compiledLocation Compiled JavaScript location * @returns Original TypeScript location or null if mapping fails */ async mapCompiledToSource(compiledLocation) { try { // Load the source map for the JavaScript file const consumer = await this.loadSourceMap(compiledLocation.file); if (!consumer) { // No source map available - return null to indicate we can't map return null; } // Map the compiled location to the original source location const original = consumer.originalPositionFor({ line: compiledLocation.line, column: compiledLocation.column, }); if (!original.source || original.line === null) { // No mapping found return null; } // Resolve the source file path // The source path in the source map is usually relative const sourceFile = this.resolveSourcePath(compiledLocation.file, original.source); return { file: sourceFile, line: original.line, column: original.column || 0, }; } catch (error) { // Handle missing or invalid source maps gracefully return null; } } /** * Find the compiled JavaScript file for a TypeScript source file * @param sourceFile Path to the TypeScript file * @returns Path to the JavaScript file or null if not found */ findCompiledFile(sourceFile) { // Try common patterns const patterns = [ sourceFile.replace(/\.ts$/, ".js"), sourceFile.replace(/\.tsx$/, ".jsx"), sourceFile.replace(/\.ts$/, ".js").replace("/src/", "/dist/"), sourceFile.replace(/\.tsx$/, ".jsx").replace("/src/", "/dist/"), ]; for (const pattern of patterns) { if (fs.existsSync(pattern)) { return pattern; } } return null; } /** * Resolve the source file path from the source map * @param jsFile Path to the JavaScript file * @param sourcePath Source path from the source map (usually relative) * @returns Absolute path to the source file */ resolveSourcePath(jsFile, sourcePath) { // If the source path is already absolute, return it if (path.isAbsolute(sourcePath)) { return sourcePath; } // Otherwise, resolve it relative to the JavaScript file's directory const jsDir = path.dirname(jsFile); return path.resolve(jsDir, sourcePath); } /** * Map a JavaScript variable name to its TypeScript source name * Uses the source map's names section for variable mapping * Requirements: 7.4 * @param jsFile Path to the JavaScript file * @param jsVariableName Variable name in the compiled JavaScript * @param line Line number where the variable is used * @param column Column number where the variable is used * @returns Original TypeScript variable name or null if mapping unavailable */ async mapVariableName(jsFile, jsVariableName, line, column) { try { // Load the source map const consumer = await this.loadSourceMap(jsFile); if (!consumer) { // No source map available - return null return null; } // Get the original position for this location const original = consumer.originalPositionFor({ line, column, }); // Check if we have a name mapping if (original.name) { return original.name; } // No name mapping available - return null to indicate we should use the JS name return null; } catch (error) { // Handle errors gracefully return null; } } /** * Get all variable names at a specific location * This can be used to map all variables in a scope * @param jsFile Path to the JavaScript file * @param line Line number * @param column Column number * @returns Array of variable names from the source map */ async getVariableNamesAtLocation(jsFile, line, column) { try { const consumer = await this.loadSourceMap(jsFile); if (!consumer) { return []; } const names = []; // Look for all mappings near this location consumer.eachMapping((mapping) => { if (mapping.generatedLine === line && Math.abs(mapping.generatedColumn - column) < 10 && // Within 10 columns mapping.name) { names.push(mapping.name); } }); return names; } catch (error) { return []; } } } exports.SourceMapManager = SourceMapManager; //# sourceMappingURL=source-map-manager.js.map