UNPKG

@paulohenriquevn/m2js

Version:

Transform TypeScript/JavaScript code into LLM-friendly Markdown summaries + Smart Dead Code Detection + Graph-Deep Diff Analysis. Extract exported functions, classes, and JSDoc comments for better AI context with 60%+ token reduction. Intelligent dead cod

364 lines 13.5 kB
"use strict"; /* eslint-disable max-lines */ /* eslint-disable max-lines-per-function */ 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; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.extractFileDependencies = extractFileDependencies; exports.analyzeDependencies = analyzeDependencies; exports.resolveModulePath = resolveModulePath; const parser_1 = require("@babel/parser"); const traverse_1 = __importDefault(require("@babel/traverse")); const t = __importStar(require("@babel/types")); const fs_1 = require("fs"); const path_1 = __importDefault(require("path")); // Babel parser configuration for dependency analysis // eslint-disable-next-line @typescript-eslint/no-explicit-any const DEPENDENCY_PARSE_CONFIG = { sourceType: 'module', plugins: [ 'typescript', 'jsx', 'decorators-legacy', 'classProperties', 'asyncGenerators', 'bigInt', 'dynamicImport', ], }; /** * Extract dependencies from a single file's AST */ function extractFileDependencies(filePath, content) { const dependencies = []; try { const ast = (0, parser_1.parse)(content, DEPENDENCY_PARSE_CONFIG); (0, traverse_1.default)(ast, { ImportDeclaration(nodePath) { const source = nodePath.node.source.value; const isExternal = !source.startsWith('./') && !source.startsWith('../') && !source.startsWith('/'); // Handle different import types nodePath.node.specifiers.forEach(spec => { if (t.isImportDefaultSpecifier(spec)) { dependencies.push({ from: filePath, to: source, type: 'import', importName: spec.local.name, isExternal, importType: 'default', }); } else if (t.isImportSpecifier(spec)) { const importName = t.isIdentifier(spec.imported) ? spec.imported.name : spec.imported.value; dependencies.push({ from: filePath, to: source, type: 'import', importName, isExternal, importType: 'named', }); } else if (t.isImportNamespaceSpecifier(spec)) { dependencies.push({ from: filePath, to: source, type: 'import', importName: spec.local.name, isExternal, importType: 'namespace', }); } }); // Handle side-effect imports (no specifiers) if (nodePath.node.specifiers.length === 0) { dependencies.push({ from: filePath, to: source, type: 'import', isExternal, importType: 'side-effect', }); } }, ExportNamedDeclaration(nodePath) { if (nodePath.node.source) { const source = nodePath.node.source.value; const isExternal = !source.startsWith('./') && !source.startsWith('../') && !source.startsWith('/'); nodePath.node.specifiers?.forEach(spec => { if (t.isExportSpecifier(spec)) { const exportName = t.isIdentifier(spec.exported) ? spec.exported.name : spec.exported.value; dependencies.push({ from: filePath, to: source, type: 'export', importName: exportName, isExternal, importType: 'named', }); } }); } }, ExportAllDeclaration(nodePath) { if (nodePath.node.source) { const source = nodePath.node.source.value; const isExternal = !source.startsWith('./') && !source.startsWith('../') && !source.startsWith('/'); dependencies.push({ from: filePath, to: source, type: 'export', isExternal, importType: 'namespace', }); } }, // Handle type-only imports TSImportType(nodePath) { if (t.isStringLiteral(nodePath.node.argument)) { const source = nodePath.node.argument.value; const isExternal = !source.startsWith('./') && !source.startsWith('../') && !source.startsWith('/'); dependencies.push({ from: filePath, to: source, type: 'type', isExternal, importType: 'named', }); } }, }); } catch (error) { // Fail-fast: throw parse errors immediately throw new Error(`Failed to parse dependencies in ${filePath}: ${error.message}`); } return dependencies; } /** * Analyze dependencies for an array of files */ function analyzeDependencies(files, options = {}) { if (files.length === 0) { throw new Error('No files provided for dependency analysis'); } const allDependencies = []; const projectPath = findCommonPath(files); // Extract dependencies from each file for (const file of files) { try { const content = (0, fs_1.readFileSync)(file, 'utf-8'); const fileDeps = extractFileDependencies(file, content); allDependencies.push(...fileDeps); } catch (error) { throw new Error(`Failed to analyze file ${file}: ${error.message}`); } } // Build unique node list const nodeSet = new Set(); files.forEach(file => nodeSet.add(file)); // Add external dependencies to nodes if requested if (options.includeExternalDeps) { allDependencies .filter(dep => dep.isExternal) .forEach(dep => nodeSet.add(dep.to)); } // Generate metrics const metrics = calculateGraphMetrics(Array.from(nodeSet), allDependencies, options); return { projectPath, nodes: Array.from(nodeSet).sort(), edges: allDependencies, metrics, }; } /** * Calculate graph metrics */ function calculateGraphMetrics(nodes, edges, options) { const internalEdges = edges.filter(edge => !edge.isExternal); const externalEdges = edges.filter(edge => edge.isExternal); // Find most connected module const connectionCounts = new Map(); internalEdges.forEach(edge => { connectionCounts.set(edge.from, (connectionCounts.get(edge.from) || 0) + 1); }); const mostConnectedModule = Array.from(connectionCounts.entries()).sort(([, a], [, b]) => b - a)[0]?.[0]; // Calculate average dependencies per module const averageDependencies = nodes.length > 0 ? Math.round((internalEdges.length / nodes.length) * 10) / 10 : 0; // Detect circular dependencies if requested const circularDependencies = options.detectCircular ? detectCircularDependencies(nodes, internalEdges) : []; return { totalNodes: nodes.length, totalEdges: edges.length, internalDependencies: internalEdges.length, externalDependencies: externalEdges.length, circularDependencies, mostConnectedModule, averageDependencies, }; } /** * Detect circular dependencies using DFS */ function detectCircularDependencies(nodes, edges) { const adjList = new Map(); const cycles = []; // Build adjacency list nodes.forEach(node => adjList.set(node, [])); edges.forEach(edge => { if (!edge.isExternal) { const targets = adjList.get(edge.from) || []; targets.push(edge.to); adjList.set(edge.from, targets); } }); // DFS to detect cycles const visited = new Set(); const recursionStack = new Set(); const path = []; function dfs(node) { if (recursionStack.has(node)) { // Found cycle - extract it from path const cycleStart = path.indexOf(node); if (cycleStart >= 0) { cycles.push([...path.slice(cycleStart), node]); } return true; } if (visited.has(node)) { return false; } visited.add(node); recursionStack.add(node); path.push(node); const neighbors = adjList.get(node) || []; for (const neighbor of neighbors) { if (dfs(neighbor)) { // Continue to find all cycles } } recursionStack.delete(node); path.pop(); return false; } // Check each node nodes.forEach(node => { if (!visited.has(node)) { dfs(node); } }); return cycles; } /** * Find common path prefix for a list of files */ function findCommonPath(files) { if (files.length === 0) return ''; if (files.length === 1) return path_1.default.dirname(files[0]); const absolutePaths = files.map(file => path_1.default.resolve(file)); const pathParts = absolutePaths.map(p => p.split(path_1.default.sep)); const commonParts = []; const minLength = Math.min(...pathParts.map(parts => parts.length)); for (let i = 0; i < minLength; i++) { const part = pathParts[0][i]; if (pathParts.every(parts => parts[i] === part)) { commonParts.push(part); } else { break; } } return commonParts.join(path_1.default.sep) || '/'; } /** * Resolve relative paths to absolute paths within project */ function resolveModulePath(fromFile, toModule) { // Handle relative imports if (toModule.startsWith('./') || toModule.startsWith('../')) { const fromDir = path_1.default.dirname(fromFile); const resolved = path_1.default.resolve(fromDir, toModule); // Try common TypeScript/JavaScript extensions const extensions = ['.ts', '.tsx', '.js', '.jsx', '.d.ts']; for (const ext of extensions) { const withExt = resolved + ext; try { (0, fs_1.readFileSync)(withExt); return withExt; } catch { // Continue trying other extensions } } // Try index files for (const ext of extensions) { const indexFile = path_1.default.join(resolved, `index${ext}`); try { (0, fs_1.readFileSync)(indexFile); return indexFile; } catch { // Continue trying other index files } } return resolved; // Return as-is if file not found } // Return external modules as-is return toModule; } //# sourceMappingURL=dependency-analyzer.js.map