@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
JavaScript
"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