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

551 lines 20.4 kB
"use strict"; /** * Dead Code Analyzer for M2JS * Detects exported functions, classes, and variables that are never imported */ 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.analyzeDeadCode = analyzeDeadCode; 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")); const suggestion_engine_1 = require("./suggestion-engine"); const performance_optimizer_1 = require("./performance-optimizer"); // Babel parser configuration for dead code analysis // eslint-disable-next-line @typescript-eslint/no-explicit-any const DEAD_CODE_PARSE_CONFIG = { sourceType: 'module', plugins: [ 'typescript', 'jsx', 'decorators-legacy', 'classProperties', 'asyncGenerators', 'bigInt', 'dynamicImport', ], }; /** * Main function to analyze dead code in multiple files */ async function analyzeDeadCode(files, performanceOptions = {}) { if (files.length === 0) { throw new Error('No files provided for dead code analysis'); } const startTime = Date.now(); const projectPath = findCommonPath(files); // Initialize performance optimizer const processor = new performance_optimizer_1.OptimizedFileProcessor(performanceOptions); const progressIndicator = performanceOptions.showProgress ? new performance_optimizer_1.ProgressIndicator() : null; // Extract exports and imports from all files using optimized processor const { allExports, allImports } = await processor.processFiles(files, (filePath, content) => { const exports = extractExports(filePath, content); const imports = extractImports(filePath, content); return { exports, imports }; }, progressIndicator ? (processed, total, currentFile) => { progressIndicator.update(processed, total, currentFile); } : undefined); // Find dead exports by cross-referencing exports vs imports (optimized) const deadExports = findDeadExportsOptimized(allExports, allImports, projectPath); // Find unused imports by checking if they're referenced in code const unusedImports = findUnusedImports(files); // Generate removal suggestions with confidence levels const suggestions = (0, suggestion_engine_1.generateRemovalSuggestions)(deadExports, unusedImports, projectPath); // Calculate metrics with performance stats const analysisTime = Date.now() - startTime; const performanceStats = processor.getPerformanceStats(); const metrics = calculateMetrics(files, allExports, allImports, deadExports, unusedImports, analysisTime, performanceStats); return { deadExports, unusedImports, suggestions, metrics, projectPath, }; } /** * Extract all exports from a file */ function extractExports(filePath, content) { const exports = []; try { const ast = (0, parser_1.parse)(content, DEAD_CODE_PARSE_CONFIG); (0, traverse_1.default)(ast, { ExportNamedDeclaration(nodePath) { processNamedExports(nodePath, filePath, exports); }, ExportDefaultDeclaration(nodePath) { processDefaultExports(nodePath, filePath, exports); }, }); } catch (error) { throw new Error(`Parse error in ${filePath}: ${error.message}`); } return exports; } /** * Process named export declarations */ function processNamedExports(nodePath, filePath, exports) { const declaration = nodePath.node.declaration; const line = nodePath.node.loc?.start.line || 0; // Handle export function/class/variable declarations if (t.isFunctionDeclaration(declaration) && declaration.id) { exports.push({ name: declaration.id.name, type: 'function', file: filePath, line, isDefault: false, }); } else if (t.isClassDeclaration(declaration) && declaration.id) { exports.push({ name: declaration.id.name, type: 'class', file: filePath, line, isDefault: false, }); } else if (t.isVariableDeclaration(declaration)) { declaration.declarations.forEach(declarator => { if (t.isIdentifier(declarator.id)) { exports.push({ name: declarator.id.name, type: 'variable', file: filePath, line, isDefault: false, }); } }); } else if (t.isTSInterfaceDeclaration(declaration)) { exports.push({ name: declaration.id.name, type: 'interface', file: filePath, line, isDefault: false, }); } else if (t.isTSTypeAliasDeclaration(declaration)) { exports.push({ name: declaration.id.name, type: 'type', file: filePath, line, isDefault: false, }); } // Handle export specifiers (export { foo, bar }) if (nodePath.node.specifiers) { nodePath.node.specifiers.forEach(specifier => { if (t.isExportSpecifier(specifier)) { const exportName = t.isIdentifier(specifier.exported) ? specifier.exported.name : specifier.exported.value; exports.push({ name: exportName, type: 'variable', // Default to variable for specifiers file: filePath, line, isDefault: false, }); } }); } } /** * Process default export declarations */ function processDefaultExports(nodePath, filePath, exports) { const declaration = nodePath.node.declaration; const line = nodePath.node.loc?.start.line || 0; if (t.isFunctionDeclaration(declaration)) { const name = declaration.id?.name || 'default'; exports.push({ name, type: 'function', file: filePath, line, isDefault: true, }); } else if (t.isClassDeclaration(declaration)) { const name = declaration.id?.name || 'default'; exports.push({ name, type: 'class', file: filePath, line, isDefault: true, }); } else if (t.isIdentifier(declaration)) { exports.push({ name: declaration.name, type: 'variable', file: filePath, line, isDefault: true, }); } } /** * Extract all imports from a file */ function extractImports(filePath, content) { const imports = []; try { const ast = (0, parser_1.parse)(content, DEAD_CODE_PARSE_CONFIG); (0, traverse_1.default)(ast, { ImportDeclaration(nodePath) { const source = nodePath.node.source.value; const line = nodePath.node.loc?.start.line || 0; // Only track relative imports (internal to project) if (source.startsWith('./') || source.startsWith('../')) { nodePath.node.specifiers.forEach(specifier => { if (t.isImportDefaultSpecifier(specifier)) { imports.push({ name: 'default', from: source, file: filePath, line, type: 'default', }); } else if (t.isImportSpecifier(specifier)) { const importName = t.isIdentifier(specifier.imported) ? specifier.imported.name : specifier.imported.value; imports.push({ name: importName, from: source, file: filePath, line, type: 'named', }); } else if (t.isImportNamespaceSpecifier(specifier)) { imports.push({ name: '*', from: source, file: filePath, line, type: 'namespace', }); } }); } }, }); } catch (error) { throw new Error(`Parse error in ${filePath}: ${error.message}`); } return imports; } /** * Optimized function to find dead exports - uses existing logic with batching */ function findDeadExportsOptimized(exports, imports, projectPath) { // Use the original logic but with performance optimizations return findDeadExports(exports, imports, projectPath); } /** * Legacy function for backward compatibility */ function findDeadExports(exports, imports, projectPath) { const deadExports = []; // Build a map of all imports for quick lookup const importMap = new Map(); imports.forEach(imp => { // Resolve relative import paths const resolvedPath = resolveImportPath(imp.file, imp.from); const importNames = importMap.get(resolvedPath) || new Set(); if (imp.type === 'default') { importNames.add('default'); } else if (imp.type === 'namespace') { importNames.add('*'); // Namespace import covers all exports } else { importNames.add(imp.name); } importMap.set(resolvedPath, importNames); }); // Check each export to see if it's imported anywhere exports.forEach(exp => { const normalizedPath = path_1.default.resolve(exp.file); const importedNames = importMap.get(normalizedPath); // Check if this export is imported const isImported = importedNames && (importedNames.has(exp.name) || importedNames.has('*') || (exp.isDefault && importedNames.has('default'))); if (!isImported) { // Assess removal risk and confidence const baseDeadExport = { file: exp.file, name: exp.name, type: exp.type, line: exp.line, reason: 'Never imported in analyzed files', confidence: 'medium', // Default, will be updated riskFactors: [], // Default, will be updated }; const riskAssessment = (0, suggestion_engine_1.assessExportRemovalRisk)(baseDeadExport, projectPath); deadExports.push({ ...baseDeadExport, confidence: riskAssessment.confidence, riskFactors: riskAssessment.riskFactors, }); } }); return deadExports; } /** * Find unused imports by checking if they're referenced in code */ function findUnusedImports(files) { const unusedImports = []; for (const file of files) { try { const content = (0, fs_1.readFileSync)(file, 'utf-8'); const fileUnusedImports = analyzeFileForUnusedImports(file, content); unusedImports.push(...fileUnusedImports); } catch (error) { // Skip files that can't be read continue; } } return unusedImports; } /** * Analyze a single file for unused imports */ function analyzeFileForUnusedImports(filePath, content) { const unusedImports = []; try { const ast = (0, parser_1.parse)(content, DEAD_CODE_PARSE_CONFIG); const imports = []; const usages = new Set(); // First pass: collect all imports (0, traverse_1.default)(ast, { ImportDeclaration(nodePath) { const source = nodePath.node.source.value; const line = nodePath.node.loc?.start.line || 0; nodePath.node.specifiers.forEach(specifier => { if (t.isImportDefaultSpecifier(specifier)) { imports.push({ name: specifier.local.name, from: source, file: filePath, line, type: 'default', }); } else if (t.isImportSpecifier(specifier)) { imports.push({ name: specifier.local.name, from: source, file: filePath, line, type: 'named', }); } else if (t.isImportNamespaceSpecifier(specifier)) { imports.push({ name: specifier.local.name, from: source, file: filePath, line, type: 'namespace', }); } }); }, }); // Second pass: collect all identifier usages (0, traverse_1.default)(ast, { Identifier(nodePath) { // Skip if this identifier is in an import declaration if (t.isImportDeclaration(nodePath.parent) || t.isImportSpecifier(nodePath.parent) || t.isImportDefaultSpecifier(nodePath.parent) || t.isImportNamespaceSpecifier(nodePath.parent)) { return; } // Skip if this is a property key in object notation if (t.isObjectProperty(nodePath.parent) && nodePath.parent.key === nodePath.node && !nodePath.parent.computed) { return; } usages.add(nodePath.node.name); }, // Handle JSX elements for React imports JSXIdentifier(nodePath) { usages.add(nodePath.node.name); }, // Handle member expressions (namespace.member) MemberExpression(nodePath) { if (t.isIdentifier(nodePath.node.object)) { usages.add(nodePath.node.object.name); } }, }); // Check which imports are unused imports.forEach(importInfo => { if (!usages.has(importInfo.name)) { // Assess removal risk and confidence const baseUnusedImport = { file: filePath, name: importInfo.name, from: importInfo.from, line: importInfo.line, type: importInfo.type, reason: 'Imported but never used in code', confidence: 'medium', // Default, will be updated riskFactors: [], // Default, will be updated }; const riskAssessment = (0, suggestion_engine_1.assessImportRemovalRisk)(baseUnusedImport); unusedImports.push({ ...baseUnusedImport, confidence: riskAssessment.confidence, riskFactors: riskAssessment.riskFactors, }); } }); } catch (error) { // Skip files with parse errors } return unusedImports; } /** * Resolve relative import path to absolute path */ function resolveImportPath(fromFile, importPath) { const fromDir = path_1.default.dirname(fromFile); const resolved = path_1.default.resolve(fromDir, importPath); // 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 path_1.default.resolve(withExt); } catch { // Continue trying other extensions } } // Try index files for (const ext of extensions.slice(0, -1)) { const indexFile = path_1.default.join(resolved, `index${ext}`); try { (0, fs_1.readFileSync)(indexFile); return path_1.default.resolve(indexFile); } catch { // Continue trying other index files } } // Return resolved path even if file not found (for analysis) return path_1.default.resolve(resolved); } /** * Calculate metrics for the dead code analysis with performance stats */ function calculateMetrics(files, allExports, allImports, deadExports, unusedImports, analysisTimeMs, performanceStats) { // Estimate savings based on average lines per export/import const avgLinesPerExport = 15; // Conservative estimate const avgLinesPerImport = 1; // Import lines are usually single line const estimatedSavingsKB = Math.round(((deadExports.length * avgLinesPerExport + unusedImports.length * avgLinesPerImport) * 50) / 1024); // ~50 chars per line const metrics = { totalFiles: files.length, totalExports: allExports.length, totalImports: allImports.length, deadExports: deadExports.length, unusedImports: unusedImports.length, analysisTimeMs, estimatedSavingsKB, }; if (performanceStats) { metrics.performanceStats = performanceStats; } return metrics; } /** * 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) || '/'; } //# sourceMappingURL=dead-code-analyzer.js.map